Guide to Interactive Visualisations
In this guide, you’ll be shown how to make 4 key interactive
visualisations, which include:
- Basic bar chart
- Grouped bar chart
- Stacked bar chart
- Scatterplot
- Scatterplot + dropdown menu
- Line graph
To create these visualisations, we’ll be using the
‘plotly’ package.
Datasets used in this workshop are from the 2021 UK census, and
involve the new voluntary question which focuses on gender identity. In
particular, we explore the relationship between age and gender
ethnicity, as well as ethnicity and gender identity.
Let’s get started by importing the necessary packages.
NOTE: If you’re not following along with Binder, and
you have your own computational environment, make sure you install the
necessary packages through the command line before proceeding to
import.
Install packages
Uncomment the lines below to install the packages if you’re not
working in Binder.
# install.packages("readr")
# install.packages("dplyr")
# install.packages("stringr")
# install.packages("shiny")
# install.packages("ggplot2")
# install.packages("plotly")
Load in packages
# Allows us to read-in csv files
library(readr)
# For data manipulation
library(dplyr)
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
# For regular expression operations
library(stringr)
# library(shiny)
library(ggplot2)
# Used tp create interactive visualisations
library(plotly)
Registered S3 method overwritten by 'data.table':
method from
print.data.table
Attaching package: ‘plotly’
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
Dataset 1
The first dataset that we’ll be focusing on is a really simple
dataset which shows the total counts for 8 gender identity categories
across England and Wales. We’ll do a bit of data cleaning, remove
unnecessary categories (such as ‘Does not apply’), and then calculate
the % of each gender identity category. Then, we’ll create a simple
interactive bar chart which displays the percentage by gender identity
category, whilst enabling some interactivity when we hover over each
bar.
# Load in dataset
df <- read_csv('../Data/GI_det_EW.csv')
Rows: 8 Columns: 5
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (3): England and Wales Code, England and Wales, Gender identity (8 categories)
dbl (2): Gender identity (8 categories) Code, Observation
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Brief glimpse of data structure
# But can also click on the dataset in the Environment pane
head(df, 10)
# Let's check out the dimensions
dim(df)
[1] 8 5
Data cleaning
- Clean column names
- Filter out unecessary categories
- Clean gender identity category values - too wordy
- Ensure gender_identity column is a factor with levels in desired
order
# str_replace_all() method finds all substrings which match the regex and replaces them with empty string
# First, let's replace any brackets with empty strings
colnames(df) <- str_replace_all(colnames(df), "\\s*\\([^)]*\\)", "")
# Lowercase column text and replace empty spaces with "_"
colnames(df) <- tolower(colnames(df))
colnames(df) <- str_replace_all(colnames(df), " ", "_")
# Let's see if it worked..
colnames(df)
[1] "england_and_wales_code" "england_and_wales" "gender_identity_code" "gender_identity" "observation"
Pipes and other operators..
So, we’ve already come across the assignment operator ‘<-’ which
is used to assign a value. E.g. df <- read_csv(‘Data/GI_age.csv’),
here we assign our csv file to a dataframe variable called ‘df’.
But, we’re now going to encounter the pipe operator ‘%>%’ which
can seem intimidating at first but is actually pretty simple. It’s used
to pass the result of one function directly into the next function. E.g.
df <- df %>% filter(gender_identity_code != -8), here we start
with our df and pass it to the filter function using the pipe operator.
This basically supplies the filter() function with its first argument,
which is the dataframe to filter on. And here we encounter a logical
operator ‘!=’ within the filter() function, which specifies that we
should only keep rows where gender_identity_code is not equal to -8.
Dollar sign operator - $
This operator is used to access elements, such as columns of a
dataframe, by name. Below, we use it to access the gender identity code
column, where we want to view the unique values.
# Get rid of columns that do not apply
df <- df %>%
filter(gender_identity_code != -8)
# Use unique and access column to output its unique values
unique(df$gender_identity_code)
[1] 1 2 3 4 5 6 7
# Let's take a look at our unique values in our gender_identity category column
unique(df$gender_identity)
[1] "Gender identity the same as sex registered at birth"
[2] "Gender identity different from sex registered at birth but no specific identity given"
[3] "Trans woman"
[4] "Trans man"
[5] "Non-binary"
[6] "All other gender identities"
[7] "Not answered"
# Use combo of mutate and recode to replace multiple values in column
# .default ensures that any value not matching those specified are left unchanged
df <- df %>%
mutate(gender_identity = recode(gender_identity,
"Gender identity the same as sex registered at birth" = "Cisgender",
"Gender identity different from sex registered at birth but no specific identity given" = "Gender identity different from sex",
.default = gender_identity))
# Let's see if it worked...
unique(df$gender_identity)
[1] "Cisgender" "Gender identity different from sex" "Trans woman"
[4] "Trans man" "Non-binary" "All other gender identities"
[7] "Not answered"
# We use factor to convert gender_identity column to a factor with specified levels
# This tells Plotly the exact order in which to display categories
df$gender_identity <- factor(df$gender_identity, levels = c(
"Cisgender",
"Gender identity different from sex",
"Trans woman",
"Trans man",
"All other gender identities",
"Not answered"
))
Question
How is gender identity represented across England and Wales?
Some subquestions that this can help us answer:
- Which gender identity category is most prevalent?
- Which gender identity is the least prevalent?
Data pre-processing
Before we can plot our data, we need to calculate the percentage of
each gender identity category. The mutate() function adds a new column
‘percentage’ to df, and applies the following calculation to each
row.
# mutate() is used to add new variables to a df or modify existing ones
df <- df %>% mutate(percentage = round(observation / sum(observation) * 100, 2))
# Let's take a look..
head(df$percentage)
[1] 93.46 0.24 0.10 0.10 0.06 0.04
Basic interactive bar chart
Now we can create our first simple interactive visualisation. To do
so we use Plotly’s plot_ly function, and supply the parameters with the
necessary arguments. You’ll notice that we use the tilde operator (~)
quite a bit when building our graph. By preceding relevant variables
with ~ it tells R to look for that variable within the dataframe.
# Create the bar chart visualization with percentages on the y-axis
fig <- plot_ly(data = df, x = ~gender_identity, y = ~percentage, type = 'bar',
# defines how the bars should be styled
marker = list(color = 'rgb(158,202,225)', line = list(color = 'rgb(8,48,107)', width = 1.5)),
width = 800, height = 600)
# Let's check it out
fig
Warning: Ignoring 1 observations
Warning: Ignoring 1 observations
Using layout() method
Once a graph has been created, we can use the layout method to
customise the appearance and layout. This allows you to modify things
such as titles, legend details, axis properties, etc, without needing to
recreate the figure from scratch.
# Let's apply a log scale to our y-axis so this graph is easier to interpet
fig <- layout(fig,
title = 'Percentage of Each Gender Identity in England and Wales',
# set showline to true, otherwise it disappears when we apply log scale
xaxis = list(title = 'Gender Identity', showline = TRUE),
yaxis = list(type = 'log', title = 'Percentage (Log Scale)'))
fig
Warning: Ignoring 1 observations
Warning: Ignoring 1 observations
Tooltips
When using different R libraries that are geared towards interactive
visualisations, you’ll often come across ‘tooltips’. These are small
boxes that provide information when a user hovers over a part of a data
visualisation such as: a point on a graph, a bar in a bar chart, or a
segment in a pie chart. They are used to display additional information
about the data point or object, providing more context without
cluttering up the chart. In Plotly tooltips are referred to as
‘hover_data’.
All interactive plotly graphs come with default hover data, so when
you scroll over a bar or a scatterplot data point it will display the
specific x-axis value and y-axis value. But, variety is the spice of
life and there’s going to be times when you want to leverage this
feature to include interesting info that isn’t included by default. For
instance, for our bar chart, I’d like to add in data from the
‘Observation’ column, which shows the raw count for each gender identity
category.
To do this it’s quite easy. We use the text and hoverinfo parameter
in the plot_ly function, with text defining the variables we’d like to
include and how they should appear, and hoverinfo ensuring that this
text is displayed in the tooltips. So, let’s create the graph again, but
this time let’s specify our tooltips.
new_fig <- plot_ly(data = df, x = ~gender_identity, y = ~percentage, type = 'bar',
# ~paste combines multiple pieces of text and data into one string
hovertext = ~paste("Gender: ", gender_identity,
#<br> is HTML code for a line break
# sprintf - used to format strings
"<br>Percentage: ", sprintf("%.2f%%", percentage),
"<br>Observations: ", observation),
# tells plotly to only display the text provided in hovertext
hoverinfo = 'text',
marker = list(color = 'rgb(158,202,225)', line = list(color = 'rgb(8,48,107)', width = 1.5)),
width = 800, height = 600)
# Apply a log scale to the y-axis
new_fig <- layout(new_fig,
title = 'Percentage of Each Gender Identity in England and Wales',
xaxis = list(title = 'Gender Identity', showline = TRUE),
yaxis = list(type = 'log', title = 'Percentage (Log Scale)'))
new_fig
Warning: Ignoring 1 observations
Warning: Ignoring 1 observations
Dataset 2
This dataset classifies residents by gender identity and age, with
the unit of analysis being England and Wales.
# Load in dataset
df2 <- read_csv('../Data/GI_age.csv')
Rows: 42 Columns: 7
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (4): England and Wales Code, England and Wales, Gender identity (7 categories), Age (6 categories)
dbl (3): Gender identity (7 categories) Code, Age (6 categories) Code, Observation
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Brief glimpse of data structure
head(df2, 10)
# Let's check out the dimensions
dim(df2)
[1] 42 7
Data Cleaning
- Clean column names
- Filter out unecessary categories
- Clean gender identity category values - too wordy
- Ensure gender_identity column is a factor with levels in desired
order
- Clean age category values - too wordy
We’ll whiz through this, because it’s the same stuff we did for the
last dataset.
# str_replace_all() method finds all substrings which match the regex and replaces them with empty string
# First, let's replace any brackets with empty strings
colnames(df2) <- str_replace_all(colnames(df2), "\\s*\\([^)]*\\)", "")
# Lowercase column text and replace empty spaces with "_"
colnames(df2) <- tolower(colnames(df2))
colnames(df2) <- str_replace_all(colnames(df2), " ", "_")
# Let's see if it worked..
colnames(df2)
[1] "england_and_wales_code" "england_and_wales" "gender_identity_code" "gender_identity" "age_code"
[6] "age" "observation"
# Get rid of columns that do not apply
df2 <- df2 %>%
filter(gender_identity_code != -8)
# Use unique and access column to output its unique values
unique(df2$gender_identity_code)
[1] 1 2 3 4 5 6
# Get rid of redundant age category
# Further filter data
df2 <- df2 %>%
filter(age_code != 1)
# Clean up the values in the 'age' column. Let's shorten them.
# Chain str_replace() calls together to apply multiple string replacements in succession
# Each str_replace() call is applied to the result of the previous one
df2$age <- df2$age %>%
str_replace('Aged ', '') %>%
str_replace('to', '-') %>%
str_replace('years', '') %>%
str_replace('and over', '+') %>%
str_replace(' - ', '-')
# We can pass our df to the select function, where we specify the column we're interested in.
# Then, we pipe the output to the head function.
df2 %>%
select(age) %>%
head()
# Use combo of mutate and recode to replace multiple values in column
# .default ensures that any value not matching those specified are left unchanged
df2 <- df2 %>%
mutate(gender_identity = recode(gender_identity,
"Gender identity the same as sex registered at birth" = "Cisgender",
"Gender identity different from sex registered at birth but no specific identity given" = "Gender identity different from sex",
.default = gender_identity))
unique(df2$gender_identity)
[1] "Cisgender" "Gender identity different from sex" "Trans woman"
[4] "Trans man" "All other gender identities" "Not answered"
# We use factor to convert gender_identity column to a factor with specified levels
# This tells Plotly the exact order in which to display categories
df2$gender_identity <- factor(df2$gender_identity, levels = c(
"Cisgender",
"Gender identity different from sex",
"Trans woman",
"Trans man",
"All other gender identities",
"Not answered"
))
Question
How is gender identity distributed among different age groups?
Some subquestions that this can help us answer:
- What % of trans women are aged 16-24 years?
- Are older age groups over represented in the ‘non-response’
category?
Data pre-processing
Calculate percentages
Below, we use the group_by function to group the data by
‘gender_identity’ and calculate the percentage within each group. Then
the mutate() function adds a new column ‘percentage’ to df, which (for
each group) divides the observation by the sum of observations,
multiplies it by 100, and rounds it up to 2 decimal points. We then use
the ungroup function when we’re done with the grouping operation.
df2 <- df2 %>%
group_by(gender_identity) %>%
mutate(percentage = round((observation / sum(observation) * 100), 2)) %>%
ungroup()
head(df2)
Interactive grouped bar chart
When creating grouped bar charts, there’s a few subtle differences
that you’ll need to account for in the code. First, we’ll need to make
sure this is a grouped bar chart, which we can set with the ‘barmode’
parameter. Second, we’ll need a way to colour each bar in each group,
according to age categories, which we can do with the ‘color’ and
‘colors’ parameters.
# Create a grouped bar chart with hover information
fig2 <- plot_ly(data = df2, x = ~gender_identity, y = ~percentage, type = 'bar',
# color specifies which variable to colour by
# colors specifies the colour palette to use, and how many colours are required
color = ~age, colors = RColorBrewer::brewer.pal(length(unique(df2$age)), "Set2"),
hoverinfo = 'text',
hovertext = ~paste("Observation: ", observation,
"<br>Percentage: ", sprintf("%.2f%%", percentage),
"<br>Age group: ", age),
marker = list(line = list(color = 'rgba(255,255,255, 0.5)', width = 0.5)),
width = 800, height = 600)
fig2
fig2 <- layout(fig2,title = 'Distribution of Gender Identity Categories Among Age Groups',
xaxis = list(title = 'Gender Identity'),
yaxis = list(title = 'Percentage'),
legend = list(title = list(text = 'Age Group')))
fig2
Stacked bar chart
The method I show below simply converts the previously made grouped
bar chart ‘fig2’ to a stacked bar chart. Stacked bar charts can only be
created using the layout() function to change the barmode, as the
default is a grouped bar chart.
# Convert to stacked bar chart
st_fig <- layout(fig2,
barmode = 'stack')
st_fig
Dataset 3
This dataset classifies residents by gender identity and ethnic
group, with the unit of analysis being the 331 local authorities across
England and Wales.
# Load in dataset
df3 <- read_csv('../Data/GI_ethnic.csv')
Rows: 10592 Columns: 7
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (4): Lower tier local authorities Code, Lower tier local authorities, Gender identity (4 categories), Ethnic group (8 categories)
dbl (3): Gender identity (4 categories) Code, Ethnic group (8 categories) Code, Observation
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Brief glimpse at underlying data structure
head(df3, 10)
Data Cleaning
- Clean column names
- Filter out unnecessary categories
Below, I provide another method ‘gsub()’ which can be used instead of
the str_replace_all() method which has been demonstrated in the previous
cleaning sections. Basically, looks for a pattern and applies the
replacement to any column names which match the pattern.
# Remove all text within parentheses from column names and replace it with an empty string
# tilde operator (~) used to apply function 'gsub' to each colname
# .x represents each colname that gsub will be applied to
df3 <- df3 %>%
rename_with(~ gsub("\\s*\\([^)]*\\)", "", .x))
# Lowercase all text in column names and replace spaces with underscores
df3 <- df3 %>%
rename_with(~ tolower(gsub(" ", "_", .x)))
# Shorten the local authority column names as they are way too long
df3 <- df3 %>%
rename(LA_code = lower_tier_local_authorities_code,
LA_name = lower_tier_local_authorities)
# Let's see if it worked
colnames(df3)
[1] "LA_code" "LA_name" "gender_identity_code" "gender_identity" "ethnic_group_code"
[6] "ethnic_group" "observation"
# Remove 'Does not apply' categories for the gender identity and ethnic group columns
df3 <- df3 %>%
filter(gender_identity_code != -8, ethnic_group_code != -8)
# Let's see if it worked..
unique(df3$gender_identity_code)
[1] 1 2 3
# Let's see if it worked..
unique(df3$ethnic_group_code)
[1] 1 2 3 4 5 6 7
Question
How does the rate of ‘non-response’ on gender identity vary among
different ethnic groups across local authorities in England and
Wales?
A subquestion this could help us answer:
Does the relationship between non-response and ethnic group % for
local authorities differ between the ‘White’ categories and other ethnic
groups?
Data pre-processing
Given that I want to explore the question above, I’d like to create a
scatterplot which explores the relationship between the % of certain
ethnic groups within local authorities and their non-response rates.
Therefore, I’ll need to prep my x and y variables, so I’ll need to
calculate the percentage of each ethnic group in each LA, and that
ethnic groups non-response rate within each LA.
Calculate % of each ethnic group in each LA
# First, we're going to group our data by LA_name, ethnic group, and sum our observations
# This leaves us with the total of each ethnic group in each local authority
ethnic_totals <- df3 %>%
group_by(LA_name, ethnic_group) %>%
summarise(Ethnic_sum = sum(observation, na.rm = TRUE)) %>%
ungroup()
`summarise()` has grouped output by 'LA_name'. You can override using the `.groups` argument.
# Print the first few rows to check
head(ethnic_totals)
# Calculate total observations for each local authority by grouping df3 by local authority and summing up obs
la_totals <- df3 %>%
group_by(LA_name) %>%
summarise(LA_sum = sum(observation, na.rm = TRUE)) %>%
ungroup()
# Print the first few rows to check
head(la_totals)
# Merge the ethnic_totals and la_totals dataframes together
# by parameter specifies which column to perform merge on
grp_pct <- merge(ethnic_totals, la_totals, by = "LA_name")
# Calculate the percentage of each ethnic group within each local authority
# Store results in new column
grp_pct <- grp_pct %>%
mutate(Percentage = round((Ethnic_sum / LA_sum * 100), 2))
# Print the first few rows to check
head(grp_pct, 10)
Calculate Ethnic Group Non-Response Rates (%’s) Within LAs
# We already have our ethnic group totals which we can re-use...
ethnic_totals
# Calculate sum of non-responses for each ethnic group within each LA
# Filter df3 so that we only have non-response rows
# Group by LA and ethnic group then sum non-response obs and store the results in new column
non_response_totals <- df3 %>%
filter(gender_identity == 'Not answered') %>%
group_by(LA_name, ethnic_group) %>%
summarise(NR_total = sum(observation, na.rm = TRUE)) %>%
ungroup()
`summarise()` has grouped output by 'LA_name'. You can override using the `.groups` argument.
# Let's check it out..
head(non_response_totals)
# Merge the tethnic group totals with the ethnic group non-response totals
# c - used when we're referencing more than one column
# all.x - performs a left join
grp_nr <- merge(ethnic_totals, non_response_totals, by = c("LA_name", "ethnic_group"), all.x = TRUE)
# Let's check it out..
head(grp_nr)
# Calculate the non-response percentage for each ethnic group within each LA
# Store results in new column
grp_nr <- grp_nr %>%
mutate(Eth_NR_Perc = round((NR_total / Ethnic_sum * 100), 2))
# Quick glance..
head(grp_nr)
Merge both datasets
Now that we’ve completed the necessary calculations, we are left with
two datasets:
- grp_pct - details the % of each ethnic_group in each LA
- grp_nr - details the ethnic group non-response % in each LA
All we need to do now then, is merge these datasets together so that
we can access the new columns and plot them:
# Merge the non-response data with the percentage of each ethnic group within each LA
# Use select to isolate columns I want to preserve in the merge, LA_sum is redundant...
nr <- merge(grp_nr, select(grp_pct, LA_name, ethnic_group, Percentage), by = c("LA_name", "ethnic_group"))
# Quick glance
head(nr)
Interactive scatterplot
In this section we’re going to:
Create a simple scatterplot exploring the relationship between
the percentage of asian citizens within local authorities and their
non-response rates
Implement a dropdown widget to update our scatterplot
# Subset dataframe so we only have responses from the asian ethnic group
asian <- nr %>%
filter(ethnic_group == 'Asian, Asian British or Asian Welsh')
# Check it out..
head(asian)
# Initialize figure
fig3 <- plot_ly(data = asian,
x = ~Percentage,
y = ~Eth_NR_Perc,
text = ~paste('LA Name:', LA_name,
'<br>Non-response Total:', NR_total,
'<br>Ethnic Group Total:', Ethnic_sum),
hoverinfo = "text",
mode = 'markers', # Specify marker points
type = 'scatter', # Graph type - scatterplot
name = 'Asian') # Default visible graph
# Customize layout
fig3 <- fig3 %>%
layout(title = 'Non-Response Rates of the Asian Ethnic Group Across Local Authorities',
xaxis = list(title = 'Percentage of Ethnic Group'),
yaxis = list(title = 'Non-response Rate'),
width = 700,
height = 700)
Warning: Specifying width/height in layout() is now deprecated.
Please specify in ggplotly() or plot_ly()
# Show the plot
fig3
Dropdown selection
What we’re going to do now, is use Plotly’s ‘updatemenus’ in
conjunction with the ‘update’ method to create a dropdown where we can
switch between the Asian ethnic group, and the White ethnic group to
make some comparisons.
Step 1: Initialise figure and add traces
We’ll start by creating a plot_ly figure with no data or variables
specified. This is because we’re going to use add_trace to add our two
sets of datapoints to the plot. ‘Traces’ refer to a set of data, so in
our example we want to add a trace with the data points relating to our
asian ethnic group, and another one for our white ethnic group. This
will start to make sense when we look at the code below.
# Initialize a Plotly figure
fig4 <- plot_ly()
# Let's take a look..
# This is our building block
fig4
Warning: No trace type specified and no positional attributes specified
No trace type specified:
Based on info supplied, a 'scatter' trace seems appropriate.
Read more about this trace type -> https://plotly.com/r/reference/#scatter
No scatter mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
Warning: No trace type specified and no positional attributes specified
No trace type specified:
Based on info supplied, a 'scatter' trace seems appropriate.
Read more about this trace type -> https://plotly.com/r/reference/#scatter
No scatter mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
# Subset dataframe so we only have responses from the white ethnic group
white <- nr %>%
filter(ethnic_group == 'White: English, Welsh, Scottish, Northern Irish or British')
# Quick check...
head(white)
# Add trace for the Asian ethnic group
fig4 <- fig4 %>% add_trace(
data = asian,
x = ~Percentage,
y = ~Eth_NR_Perc,
text = ~paste('LA Name:', LA_name,
'<br>Non-response Total:', NR_total,
'<br>Ethnic Group Total:', Ethnic_sum),
type = 'scatter',
mode = 'markers',
name = 'Asian',
hoverinfo = 'text',
visible = T
)
# Add trace for the White ethnic group
fig4 <- fig4 %>% add_trace(
data = white,
x = ~Percentage,
y = ~Eth_NR_Perc,
text = ~paste('LA Name:', LA_name,
'<br>Non-response Total:', NR_total,
'<br>Ethnic Group Total:', Ethnic_sum),
type = 'scatter',
mode = 'markers',
name = 'White',
hoverinfo = 'text',
visible = F
)
fig4
Dataset 3
This dataset includes sexual identity estimates by gender from 2010
to 2014. This is presented at a UK level, and broken down by England,
Wales, Scotland and Northern Ireland. I wanted this guide to include a
demo of how to make interactive line graphs with gender identity data,
but unfortunately given this is only the first year that the ONS has
collected this data that was not possible. So I found a dataset from
2015 which involves experimental statistics that have been used in the
Integrated Household Survey. For more info, you can check out this ONS
link.
# Load in dataset
df3 <- read_csv('../Data/cleaned_sexuality_df.csv')
Rows: 300 Columns: 5
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (3): Country, Gender, Sexuality
dbl (2): Year, Percentage
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Brief glimpse at underlying data structure
head(df3, 10)
Data cleaning
When I first found this dataset it was very messy and formatted
terribly, so I performed some cleaning on it in a separate jupyter
notebook, to save cluttering this one and distracting from the main
tutorial. If you’d like to see how I cleaned it up, please see the ‘Data_cleaning_sexuality.ipynb’
notebook.
Data pre-processing
The only pre-processing we’re going to do is subset our data by
country, and also create 2 separate datasets for Gender = Men and Gender
= Women. I’ll explain why this step is needed soon.
# Filter dataset to focus on England
england_df <- df3 %>%
filter(Country == 'England')
# Let's check it worked..
unique(england_df$Country)
# Further filter data for each gender
men <- england_df %>% filter(Gender == "Men")
women <- england_df %>% filter(Gender == "Women")
# Let's check it worked
unique(men$Gender)
[1] "Men"
unique(women$Gender)
[1] "Women"
Interactive linegraph
Creating a simple line graph in plotly is pretty easy, but where
plotly struggles (in R) is in handling facet plots. A facet plot is a
type of visualisation that divides data into subplots based on
categorical variables. What I’d like to do is create a facet plot of
sexuality percentages in England (2010-2014) with individual subplots
for our two genders. This is achieved easily in Python due to the
plotly.express module, which provides a simple way to create facet
plots. Unfortunately, we’ll have to go through a bit more of a
longwinded route, where we’ll manually create our individual plots for
each gender, then combine them using the subplot function. Also,
plotly.express automatically manages legends to ensure they’re unified
across facets, but R’s plotly requires that we manually sync up these
legends. Womp womp. Let’s get to it.
# Create individual plot for each gender
# Create plots for each gender
men_plot <- plot_ly(men,
x = ~Year,
y = ~Percentage,
color = ~Sexuality,
type = 'scatter',
# mode used to make sure our data points are connected by lines across the years
mode = 'lines+markers',
hoverinfo = 'text',
text = ~paste("Year:", Year, "<br>Percentage:", Percentage, "<br>Sexuality:", Sexuality),
# legendgroup parameter ensures that data points relating to the same category are synced across plots
legendgroup = ~Sexuality,
# showlegend parameter set to TRUE only for this plot to avoid duplicate legends
showlegend = TRUE) %>%
layout(xaxis = list(title = 'Year', tickvals = 2010:2014, ticktext = 2010:2014),
yaxis = list(title = 'Percentage'),
# Here we add an annotation to the graph to label the first subplot "Men"
# Setting xref and yref to 'paper' simply means the annotation won't move if we zoom in or out
annotations = list(
list(x = 0.5, y = 1.05, text = "Men", showarrow = FALSE, xref='paper', yref='paper')))
women_plot <- plot_ly(women,
x = ~Year,
y = ~Percentage,
color = ~Sexuality,
type = 'scatter',
mode = 'lines+markers',
hoverinfo = 'text',
text = ~paste("Year:", Year, "<br>Percentage:", Percentage, "<br>Sexuality:", Sexuality),
legendgroup = ~Sexuality,
showlegend = FALSE) %>%
layout(xaxis = list(title = 'Year', tickvals = 2010:2014, ticktext = 2010:2014),
yaxis = list(title = 'Percentage'),
annotations = list(
list(x = 0.5, y = 1.05, text = "Women", showarrow = FALSE, xref='paper', yref='paper')))
# Let's take a look at one of these graphs
women_plot
# Combine individual plots using subplot
# Within subplot, define number of rows, make sure share same x axes and both axes titles
fig5 <- subplot(men_plot, women_plot, nrows = 2, shareX = TRUE, titleX = TRUE, titleY = TRUE) %>%
layout(
title = list(
text = 'Sexuality Percentages by Gender in England (2010-2014)',
y = 0.98, # Move the title higher up
x = 0.5, # Center the title
xanchor = "center",
yanchor = "top"
),
margin = list(t = 100), # Add space at the top for the title
height = 800,
width = 1000
)
Warning: Specifying width/height in layout() is now deprecated.
Please specify in ggplotly() or plot_ly()
fig5
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIVtVS0RTIGxvZ29dKC9Vc2Vycy9sb3VjYXAvRG9jdW1lbnRzL0dpdFdvcmsvSW50ZXJhY3RpdmVfdmlzX25ldy9JbWFnZXMvdWtkcy5wbmcpCgojIEd1aWRlIHRvIEludGVyYWN0aXZlIFZpc3VhbGlzYXRpb25zCgpJbiB0aGlzIGd1aWRlLCB5b3UnbGwgYmUgc2hvd24gaG93IHRvIG1ha2UgNCBrZXkgaW50ZXJhY3RpdmUgdmlzdWFsaXNhdGlvbnMsIHdoaWNoIGluY2x1ZGU6IAoKKiBCYXNpYyBiYXIgY2hhcnQKKiBHcm91cGVkIGJhciBjaGFydAoqIFN0YWNrZWQgYmFyIGNoYXJ0CiogU2NhdHRlcnBsb3QKKiBTY2F0dGVycGxvdCArIGRyb3Bkb3duIG1lbnUKKiBMaW5lIGdyYXBoCgpUbyBjcmVhdGUgdGhlc2UgdmlzdWFsaXNhdGlvbnMsIHdlJ2xsIGJlIHVzaW5nIHRoZSAqKidwbG90bHknKiogcGFja2FnZS4gCgpEYXRhc2V0cyB1c2VkIGluIHRoaXMgd29ya3Nob3AgYXJlIGZyb20gdGhlIDIwMjEgVUsgY2Vuc3VzLCBhbmQgaW52b2x2ZSB0aGUgbmV3IHZvbHVudGFyeSBxdWVzdGlvbiB3aGljaCBmb2N1c2VzIG9uIGdlbmRlciBpZGVudGl0eS4gSW4gcGFydGljdWxhciwgd2UgZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYWdlIGFuZCBnZW5kZXIgZXRobmljaXR5LCBhcyB3ZWxsIGFzIGV0aG5pY2l0eSBhbmQgZ2VuZGVyIGlkZW50aXR5LiAKCkxldCdzIGdldCBzdGFydGVkIGJ5IGltcG9ydGluZyB0aGUgbmVjZXNzYXJ5IHBhY2thZ2VzLgoKKipOT1RFOioqIElmIHlvdSdyZSBub3QgZm9sbG93aW5nIGFsb25nIHdpdGggQmluZGVyLCBhbmQgeW91IGhhdmUgeW91ciBvd24gY29tcHV0YXRpb25hbCBlbnZpcm9ubWVudCwgbWFrZSBzdXJlIHlvdSBpbnN0YWxsIHRoZSBuZWNlc3NhcnkgcGFja2FnZXMgdGhyb3VnaCB0aGUgY29tbWFuZCBsaW5lIGJlZm9yZSBwcm9jZWVkaW5nIHRvIGltcG9ydC4gCgojIyBJbnN0YWxsIHBhY2thZ2VzCgpVbmNvbW1lbnQgdGhlIGxpbmVzIGJlbG93IHRvIGluc3RhbGwgdGhlIHBhY2thZ2VzIGlmIHlvdSdyZSBub3Qgd29ya2luZyBpbiBCaW5kZXIuCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkciIpCiMgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQojIGluc3RhbGwucGFja2FnZXMoInN0cmluZ3IiKQojIGluc3RhbGwucGFja2FnZXMoInNoaW55IikKIyBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikKIyBpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKQpgYGAKCiMjIExvYWQgaW4gcGFja2FnZXMKCmBgYHtyfQojIEFsbG93cyB1cyB0byByZWFkLWluIGNzdiBmaWxlcwpsaWJyYXJ5KHJlYWRyKSAKIyBGb3IgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShkcGx5cikgCiMgRm9yIHJlZ3VsYXIgZXhwcmVzc2lvbiBvcGVyYXRpb25zIApsaWJyYXJ5KHN0cmluZ3IpIAojIGxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoZ2dwbG90MikKIyBVc2VkIHRwIGNyZWF0ZSBpbnRlcmFjdGl2ZSB2aXN1YWxpc2F0aW9ucwpsaWJyYXJ5KHBsb3RseSkKYGBgCgojIERhdGFzZXQgMQoKVGhlIGZpcnN0IGRhdGFzZXQgdGhhdCB3ZSdsbCBiZSBmb2N1c2luZyBvbiBpcyBhIHJlYWxseSBzaW1wbGUgZGF0YXNldCB3aGljaCBzaG93cyB0aGUgdG90YWwgY291bnRzIGZvciA4IGdlbmRlciBpZGVudGl0eSBjYXRlZ29yaWVzIGFjcm9zcyBFbmdsYW5kIGFuZCBXYWxlcy4gV2UnbGwgZG8gYSBiaXQgb2YgZGF0YSBjbGVhbmluZywgcmVtb3ZlIHVubmVjZXNzYXJ5IGNhdGVnb3JpZXMgKHN1Y2ggYXMgJ0RvZXMgbm90IGFwcGx5JyksIGFuZCB0aGVuIGNhbGN1bGF0ZSB0aGUgJSBvZiBlYWNoIGdlbmRlciBpZGVudGl0eSBjYXRlZ29yeS4gVGhlbiwgd2UnbGwgY3JlYXRlIGEgc2ltcGxlIGludGVyYWN0aXZlIGJhciBjaGFydCB3aGljaCBkaXNwbGF5cyB0aGUgcGVyY2VudGFnZSBieSBnZW5kZXIgaWRlbnRpdHkgY2F0ZWdvcnksIHdoaWxzdCBlbmFibGluZyBzb21lIGludGVyYWN0aXZpdHkgd2hlbiB3ZSBob3ZlciBvdmVyIGVhY2ggYmFyLgoKYGBge3J9CiMgTG9hZCBpbiBkYXRhc2V0CgpkZiA8LSByZWFkX2NzdignLi4vRGF0YS9HSV9kZXRfRVcuY3N2JykKYGBgCmBgYHtyfQojIEJyaWVmIGdsaW1wc2Ugb2YgZGF0YSBzdHJ1Y3R1cmUKIyBCdXQgY2FuIGFsc28gY2xpY2sgb24gdGhlIGRhdGFzZXQgaW4gdGhlIEVudmlyb25tZW50IHBhbmUKaGVhZChkZiwgMTApCmBgYAoKYGBge3J9CiMgTGV0J3MgY2hlY2sgb3V0IHRoZSBkaW1lbnNpb25zCmRpbShkZikKYGBgCgojIyBEYXRhIGNsZWFuaW5nCgoqIENsZWFuIGNvbHVtbiBuYW1lcwoqIEZpbHRlciBvdXQgdW5lY2Vzc2FyeSBjYXRlZ29yaWVzCiogQ2xlYW4gZ2VuZGVyIGlkZW50aXR5IGNhdGVnb3J5IHZhbHVlcyAtIHRvbyB3b3JkeQoqIEVuc3VyZSBnZW5kZXJfaWRlbnRpdHkgY29sdW1uIGlzIGEgZmFjdG9yIHdpdGggbGV2ZWxzIGluIGRlc2lyZWQgb3JkZXIKCgpgYGB7cn0KIyBzdHJfcmVwbGFjZV9hbGwoKSBtZXRob2QgZmluZHMgYWxsIHN1YnN0cmluZ3Mgd2hpY2ggbWF0Y2ggdGhlIHJlZ2V4IGFuZCByZXBsYWNlcyB0aGVtIHdpdGggZW1wdHkgc3RyaW5nCiMgRmlyc3QsIGxldCdzIHJlcGxhY2UgYW55IGJyYWNrZXRzIHdpdGggZW1wdHkgc3RyaW5ncwpjb2xuYW1lcyhkZikgPC0gc3RyX3JlcGxhY2VfYWxsKGNvbG5hbWVzKGRmKSwgIlxccypcXChbXildKlxcKSIsICIiKQoKIyBMb3dlcmNhc2UgY29sdW1uIHRleHQgYW5kIHJlcGxhY2UgZW1wdHkgc3BhY2VzIHdpdGggIl8iCmNvbG5hbWVzKGRmKSA8LSB0b2xvd2VyKGNvbG5hbWVzKGRmKSkKY29sbmFtZXMoZGYpIDwtIHN0cl9yZXBsYWNlX2FsbChjb2xuYW1lcyhkZiksICIgIiwgIl8iKQoKIyBMZXQncyBzZWUgaWYgaXQgd29ya2VkLi4KY29sbmFtZXMoZGYpCgpgYGAKCgojIyMgUGlwZXMgYW5kIG90aGVyIG9wZXJhdG9ycy4uCgpTbywgd2UndmUgYWxyZWFkeSBjb21lIGFjcm9zcyB0aGUgYXNzaWdubWVudCBvcGVyYXRvciAnPC0nIHdoaWNoIGlzIHVzZWQgdG8gYXNzaWduIGEgdmFsdWUuIEUuZy4gZGYgPC0gcmVhZF9jc3YoJ0RhdGEvR0lfYWdlLmNzdicpLCBoZXJlIHdlIGFzc2lnbiBvdXIgY3N2IGZpbGUgdG8gYSBkYXRhZnJhbWUgdmFyaWFibGUgY2FsbGVkICdkZicuCgpCdXQsIHdlJ3JlIG5vdyBnb2luZyB0byBlbmNvdW50ZXIgdGhlIHBpcGUgb3BlcmF0b3IgJyU+JScgd2hpY2ggY2FuIHNlZW0gaW50aW1pZGF0aW5nIGF0IGZpcnN0IGJ1dCBpcyBhY3R1YWxseSBwcmV0dHkgc2ltcGxlLiBJdCdzIHVzZWQgdG8gcGFzcyB0aGUgcmVzdWx0IG9mIG9uZSBmdW5jdGlvbiBkaXJlY3RseSBpbnRvIHRoZSBuZXh0IGZ1bmN0aW9uLiBFLmcuIGRmIDwtIGRmICU+JSBmaWx0ZXIoZ2VuZGVyX2lkZW50aXR5X2NvZGUgIT0gLTgpLCBoZXJlIHdlIHN0YXJ0IHdpdGggb3VyIGRmIGFuZCBwYXNzIGl0IHRvIHRoZSBmaWx0ZXIgZnVuY3Rpb24gdXNpbmcgdGhlIHBpcGUgb3BlcmF0b3IuIFRoaXMgYmFzaWNhbGx5IHN1cHBsaWVzIHRoZSBmaWx0ZXIoKSBmdW5jdGlvbiB3aXRoIGl0cyBmaXJzdCBhcmd1bWVudCwgd2hpY2ggaXMgdGhlIGRhdGFmcmFtZSB0byBmaWx0ZXIgb24uIEFuZCBoZXJlIHdlIGVuY291bnRlciBhIGxvZ2ljYWwgb3BlcmF0b3IgJyE9JyB3aXRoaW4gdGhlIGZpbHRlcigpIGZ1bmN0aW9uLCB3aGljaCBzcGVjaWZpZXMgdGhhdCB3ZSBzaG91bGQgb25seSBrZWVwIHJvd3Mgd2hlcmUgZ2VuZGVyX2lkZW50aXR5X2NvZGUgaXMgbm90IGVxdWFsIHRvIC04LiAKCiMjIyBEb2xsYXIgc2lnbiBvcGVyYXRvciAtICQKClRoaXMgb3BlcmF0b3IgaXMgdXNlZCB0byBhY2Nlc3MgZWxlbWVudHMsIHN1Y2ggYXMgY29sdW1ucyBvZiBhIGRhdGFmcmFtZSwgYnkgbmFtZS4KQmVsb3csIHdlIHVzZSBpdCB0byBhY2Nlc3MgdGhlIGdlbmRlciBpZGVudGl0eSBjb2RlIGNvbHVtbiwgd2hlcmUgd2Ugd2FudCB0byB2aWV3IHRoZSB1bmlxdWUgdmFsdWVzLgoKYGBge3J9CiMgR2V0IHJpZCBvZiBjb2x1bW5zIHRoYXQgZG8gbm90IGFwcGx5CmRmIDwtIGRmICU+JSAKICBmaWx0ZXIoZ2VuZGVyX2lkZW50aXR5X2NvZGUgIT0gLTgpIAoKIyBVc2UgdW5pcXVlIGFuZCBhY2Nlc3MgY29sdW1uIHRvIG91dHB1dCBpdHMgdW5pcXVlIHZhbHVlcwoKdW5pcXVlKGRmJGdlbmRlcl9pZGVudGl0eV9jb2RlKQpgYGAKCmBgYHtyfQojIExldCdzIHRha2UgYSBsb29rIGF0IG91ciB1bmlxdWUgdmFsdWVzIGluIG91ciBnZW5kZXJfaWRlbnRpdHkgY2F0ZWdvcnkgY29sdW1uCgp1bmlxdWUoZGYkZ2VuZGVyX2lkZW50aXR5KQpgYGAKYGBge3J9CiMgVXNlIGNvbWJvIG9mIG11dGF0ZSBhbmQgcmVjb2RlIHRvIHJlcGxhY2UgbXVsdGlwbGUgdmFsdWVzIGluIGNvbHVtbgojIC5kZWZhdWx0IGVuc3VyZXMgdGhhdCBhbnkgdmFsdWUgbm90IG1hdGNoaW5nIHRob3NlIHNwZWNpZmllZCBhcmUgbGVmdCB1bmNoYW5nZWQKCmRmIDwtIGRmICU+JQogIG11dGF0ZShnZW5kZXJfaWRlbnRpdHkgPSByZWNvZGUoZ2VuZGVyX2lkZW50aXR5LAogICAgIkdlbmRlciBpZGVudGl0eSB0aGUgc2FtZSBhcyBzZXggcmVnaXN0ZXJlZCBhdCBiaXJ0aCIgPSAiQ2lzZ2VuZGVyIiwKICAgICJHZW5kZXIgaWRlbnRpdHkgZGlmZmVyZW50IGZyb20gc2V4IHJlZ2lzdGVyZWQgYXQgYmlydGggYnV0IG5vIHNwZWNpZmljIGlkZW50aXR5IGdpdmVuIiA9ICJHZW5kZXIgaWRlbnRpdHkgZGlmZmVyZW50IGZyb20gc2V4IiwKICAgIC5kZWZhdWx0ID0gZ2VuZGVyX2lkZW50aXR5KSkKYGBgCgpgYGB7cn0KIyBMZXQncyBzZWUgaWYgaXQgd29ya2VkLi4uCnVuaXF1ZShkZiRnZW5kZXJfaWRlbnRpdHkpCmBgYAoKYGBge3J9CiMgV2UgdXNlIGZhY3RvciB0byBjb252ZXJ0IGdlbmRlcl9pZGVudGl0eSBjb2x1bW4gdG8gYSBmYWN0b3Igd2l0aCBzcGVjaWZpZWQgbGV2ZWxzIAojIFRoaXMgdGVsbHMgUGxvdGx5IHRoZSBleGFjdCBvcmRlciBpbiB3aGljaCB0byBkaXNwbGF5IGNhdGVnb3JpZXMKCmRmJGdlbmRlcl9pZGVudGl0eSA8LSBmYWN0b3IoZGYkZ2VuZGVyX2lkZW50aXR5LCBsZXZlbHMgPSBjKAogICJDaXNnZW5kZXIiLAogICJHZW5kZXIgaWRlbnRpdHkgZGlmZmVyZW50IGZyb20gc2V4IiwKICAiVHJhbnMgd29tYW4iLAogICJUcmFucyBtYW4iLAogICJBbGwgb3RoZXIgZ2VuZGVyIGlkZW50aXRpZXMiLAogICJOb3QgYW5zd2VyZWQiCikpCmBgYAoKIyMgUXVlc3Rpb24KCkhvdyBpcyBnZW5kZXIgaWRlbnRpdHkgcmVwcmVzZW50ZWQgYWNyb3NzIEVuZ2xhbmQgYW5kIFdhbGVzPwoKU29tZSBzdWJxdWVzdGlvbnMgdGhhdCB0aGlzIGNhbiBoZWxwIHVzIGFuc3dlcjoKCiogV2hpY2ggZ2VuZGVyIGlkZW50aXR5IGNhdGVnb3J5IGlzIG1vc3QgcHJldmFsZW50PwoqIFdoaWNoIGdlbmRlciBpZGVudGl0eSBpcyB0aGUgbGVhc3QgcHJldmFsZW50PwoKIyMgRGF0YSBwcmUtcHJvY2Vzc2luZwoKQmVmb3JlIHdlIGNhbiBwbG90IG91ciBkYXRhLCB3ZSBuZWVkIHRvIGNhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiBlYWNoIGdlbmRlciBpZGVudGl0eSBjYXRlZ29yeS4gClRoZSBtdXRhdGUoKSBmdW5jdGlvbiBhZGRzIGEgbmV3IGNvbHVtbiAncGVyY2VudGFnZScgdG8gZGYsIGFuZCBhcHBsaWVzIHRoZSBmb2xsb3dpbmcgY2FsY3VsYXRpb24gdG8gZWFjaCByb3cuCgpgYGB7cn0KIyBtdXRhdGUoKSBpcyB1c2VkIHRvIGFkZCBuZXcgdmFyaWFibGVzIHRvIGEgZGYgb3IgbW9kaWZ5IGV4aXN0aW5nIG9uZXMKZGYgPC0gZGYgJT4lIG11dGF0ZShwZXJjZW50YWdlID0gcm91bmQob2JzZXJ2YXRpb24gLyBzdW0ob2JzZXJ2YXRpb24pICogMTAwLCAyKSkKYGBgCgpgYGB7cn0KIyBMZXQncyB0YWtlIGEgbG9vay4uCmhlYWQoZGYkcGVyY2VudGFnZSkKYGBgCgoKCiMjIEJhc2ljIGludGVyYWN0aXZlIGJhciBjaGFydAoKTm93IHdlIGNhbiBjcmVhdGUgb3VyIGZpcnN0IHNpbXBsZSBpbnRlcmFjdGl2ZSB2aXN1YWxpc2F0aW9uLiBUbyBkbyBzbyB3ZSB1c2UgUGxvdGx5J3MgcGxvdF9seSBmdW5jdGlvbiwgYW5kIHN1cHBseSB0aGUgcGFyYW1ldGVycyB3aXRoIHRoZSBuZWNlc3NhcnkgYXJndW1lbnRzLiBZb3UnbGwgbm90aWNlIHRoYXQgd2UgdXNlIHRoZSB0aWxkZSBvcGVyYXRvciAofikgcXVpdGUgYSBiaXQgd2hlbiBidWlsZGluZyBvdXIgZ3JhcGguIEJ5IHByZWNlZGluZyByZWxldmFudCB2YXJpYWJsZXMgd2l0aCB+IGl0IHRlbGxzIFIgdG8gbG9vayBmb3IgdGhhdCB2YXJpYWJsZSB3aXRoaW4gdGhlIGRhdGFmcmFtZS4KCgpgYGB7cn0KIyBDcmVhdGUgdGhlIGJhciBjaGFydCB2aXN1YWxpemF0aW9uIHdpdGggcGVyY2VudGFnZXMgb24gdGhlIHktYXhpcwpmaWcgPC0gcGxvdF9seShkYXRhID0gZGYsIHggPSB+Z2VuZGVyX2lkZW50aXR5LCB5ID0gfnBlcmNlbnRhZ2UsIHR5cGUgPSAnYmFyJywKICAgICAgICAgICAgICAgIyBkZWZpbmVzIGhvdyB0aGUgYmFycyBzaG91bGQgYmUgc3R5bGVkCiAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiKDE1OCwyMDIsMjI1KScsIGxpbmUgPSBsaXN0KGNvbG9yID0gJ3JnYig4LDQ4LDEwNyknLCB3aWR0aCA9IDEuNSkpLAogICAgICAgICAgICAgICB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gNjAwKQpgYGAKCgpgYGB7cn0KIyBMZXQncyBjaGVjayBpdCBvdXQKZmlnCgpgYGAKCiMjIFVzaW5nIGxheW91dCgpIG1ldGhvZAoKT25jZSBhIGdyYXBoIGhhcyBiZWVuIGNyZWF0ZWQsIHdlIGNhbiB1c2UgdGhlIGxheW91dCBtZXRob2QgdG8gY3VzdG9taXNlIHRoZSBhcHBlYXJhbmNlIGFuZCBsYXlvdXQuIFRoaXMgYWxsb3dzIHlvdSB0byBtb2RpZnkgdGhpbmdzIHN1Y2ggYXMgdGl0bGVzLCBsZWdlbmQgZGV0YWlscywgYXhpcyBwcm9wZXJ0aWVzLCBldGMsIHdpdGhvdXQgbmVlZGluZyB0byByZWNyZWF0ZSB0aGUgZmlndXJlIGZyb20gc2NyYXRjaC4KCmBgYHtyfQojIExldCdzIGFwcGx5IGEgbG9nIHNjYWxlIHRvIG91ciB5LWF4aXMgc28gdGhpcyBncmFwaCBpcyBlYXNpZXIgdG8gaW50ZXJwZXQgCgpmaWcgPC0gbGF5b3V0KGZpZywKICAgICAgICAgICAgICB0aXRsZSA9ICdQZXJjZW50YWdlIG9mIEVhY2ggR2VuZGVyIElkZW50aXR5IGluIEVuZ2xhbmQgYW5kIFdhbGVzJywKICAgICAgICAgICAgICAjIHNldCBzaG93bGluZSB0byB0cnVlLCBvdGhlcndpc2UgaXQgZGlzYXBwZWFycyB3aGVuIHdlIGFwcGx5IGxvZyBzY2FsZQogICAgICAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdHZW5kZXIgSWRlbnRpdHknLCBzaG93bGluZSA9IFRSVUUpLAogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0eXBlID0gJ2xvZycsIHRpdGxlID0gJ1BlcmNlbnRhZ2UgKExvZyBTY2FsZSknKSkKYGBgCgpgYGB7cn0KZmlnCmBgYAoKIyMgVG9vbHRpcHMKCldoZW4gdXNpbmcgZGlmZmVyZW50IFIgbGlicmFyaWVzIHRoYXQgYXJlIGdlYXJlZCB0b3dhcmRzIGludGVyYWN0aXZlIHZpc3VhbGlzYXRpb25zLCB5b3UnbGwgb2Z0ZW4gY29tZSBhY3Jvc3MgJ3Rvb2x0aXBzJy4gVGhlc2UgYXJlIHNtYWxsIGJveGVzIHRoYXQgcHJvdmlkZSBpbmZvcm1hdGlvbiB3aGVuIGEgdXNlciBob3ZlcnMgb3ZlciBhIHBhcnQgb2YgYSBkYXRhIHZpc3VhbGlzYXRpb24gc3VjaCBhczogYSBwb2ludCBvbiBhIGdyYXBoLCBhIGJhciBpbiBhIGJhciBjaGFydCwgb3IgYSBzZWdtZW50IGluIGEgcGllIGNoYXJ0LiBUaGV5IGFyZSB1c2VkIHRvIGRpc3BsYXkgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgZGF0YSBwb2ludCBvciBvYmplY3QsIHByb3ZpZGluZyBtb3JlIGNvbnRleHQgd2l0aG91dCBjbHV0dGVyaW5nIHVwIHRoZSBjaGFydC4gSW4gUGxvdGx5IHRvb2x0aXBzIGFyZSByZWZlcnJlZCB0byBhcyAnaG92ZXJfZGF0YScuCgpBbGwgaW50ZXJhY3RpdmUgcGxvdGx5IGdyYXBocyBjb21lIHdpdGggZGVmYXVsdCBob3ZlciBkYXRhLCBzbyB3aGVuIHlvdSBzY3JvbGwgb3ZlciBhIGJhciBvciBhIHNjYXR0ZXJwbG90IGRhdGEgcG9pbnQgaXQgd2lsbCBkaXNwbGF5IHRoZSBzcGVjaWZpYyB4LWF4aXMgdmFsdWUgYW5kIHktYXhpcyB2YWx1ZS4gQnV0LCB2YXJpZXR5IGlzIHRoZSBzcGljZSBvZiBsaWZlIGFuZCB0aGVyZSdzIGdvaW5nIHRvIGJlIHRpbWVzIHdoZW4geW91IHdhbnQgdG8gbGV2ZXJhZ2UgdGhpcyBmZWF0dXJlIHRvIGluY2x1ZGUgaW50ZXJlc3RpbmcgaW5mbyB0aGF0IGlzbid0IGluY2x1ZGVkIGJ5IGRlZmF1bHQuIEZvciBpbnN0YW5jZSwgZm9yIG91ciBiYXIgY2hhcnQsIEknZCBsaWtlIHRvIGFkZCBpbiBkYXRhIGZyb20gdGhlICdPYnNlcnZhdGlvbicgY29sdW1uLCB3aGljaCBzaG93cyB0aGUgcmF3IGNvdW50IGZvciBlYWNoIGdlbmRlciBpZGVudGl0eSBjYXRlZ29yeS4KClRvIGRvIHRoaXMgaXQncyBxdWl0ZSBlYXN5LiBXZSB1c2UgdGhlIHRleHQgYW5kIGhvdmVyaW5mbyBwYXJhbWV0ZXIgaW4gdGhlIHBsb3RfbHkgZnVuY3Rpb24sIHdpdGggdGV4dCBkZWZpbmluZyB0aGUgdmFyaWFibGVzIHdlJ2QgbGlrZSB0byBpbmNsdWRlIGFuZCBob3cgdGhleSBzaG91bGQgYXBwZWFyLCBhbmQgaG92ZXJpbmZvIGVuc3VyaW5nIHRoYXQgdGhpcyB0ZXh0IGlzIGRpc3BsYXllZCBpbiB0aGUgdG9vbHRpcHMuIFNvLCBsZXQncyBjcmVhdGUgdGhlIGdyYXBoIGFnYWluLCBidXQgdGhpcyB0aW1lIGxldCdzIHNwZWNpZnkgb3VyIHRvb2x0aXBzLgoKYGBge3J9Cm5ld19maWcgPC0gcGxvdF9seShkYXRhID0gZGYsIHggPSB+Z2VuZGVyX2lkZW50aXR5LCB5ID0gfnBlcmNlbnRhZ2UsIHR5cGUgPSAnYmFyJywKICAgICAgICAgICAgICAgICAgICMgfnBhc3RlICBjb21iaW5lcyBtdWx0aXBsZSBwaWVjZXMgb2YgdGV4dCBhbmQgZGF0YSBpbnRvIG9uZSBzdHJpbmcKICAgICAgICAgICAgICAgaG92ZXJ0ZXh0ID0gfnBhc3RlKCJHZW5kZXI6ICIsIGdlbmRlcl9pZGVudGl0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjPGJyPiBpcyBIVE1MIGNvZGUgZm9yIGEgbGluZSBicmVhawogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzcHJpbnRmIC0gdXNlZCB0byBmb3JtYXQgc3RyaW5ncwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8YnI+UGVyY2VudGFnZTogIiwgc3ByaW50ZigiJS4yZiUlIiwgcGVyY2VudGFnZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8YnI+T2JzZXJ2YXRpb25zOiAiLCBvYnNlcnZhdGlvbiksCiAgICAgICAgICAgICAgICMgdGVsbHMgcGxvdGx5IHRvIG9ubHkgZGlzcGxheSB0aGUgdGV4dCBwcm92aWRlZCBpbiBob3ZlcnRleHQKICAgICAgICAgICAgICAgaG92ZXJpbmZvID0gJ3RleHQnLAogICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJ3JnYigxNTgsMjAyLDIyNSknLCBsaW5lID0gbGlzdChjb2xvciA9ICdyZ2IoOCw0OCwxMDcpJywgd2lkdGggPSAxLjUpKSwKICAgICAgICAgICAgICAgd2lkdGggPSA4MDAsIGhlaWdodCA9IDYwMCkKCiMgQXBwbHkgYSBsb2cgc2NhbGUgdG8gdGhlIHktYXhpcwpuZXdfZmlnIDwtIGxheW91dChuZXdfZmlnLAogICAgICAgICAgICAgIHRpdGxlID0gJ1BlcmNlbnRhZ2Ugb2YgRWFjaCBHZW5kZXIgSWRlbnRpdHkgaW4gRW5nbGFuZCBhbmQgV2FsZXMnLAogICAgICAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdHZW5kZXIgSWRlbnRpdHknLCBzaG93bGluZSA9IFRSVUUpLAogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0eXBlID0gJ2xvZycsIHRpdGxlID0gJ1BlcmNlbnRhZ2UgKExvZyBTY2FsZSknKSkKCmBgYAoKYGBge3J9Cm5ld19maWcKYGBgCgoKCgojIERhdGFzZXQgMgoKVGhpcyBkYXRhc2V0IGNsYXNzaWZpZXMgcmVzaWRlbnRzIGJ5IGdlbmRlciBpZGVudGl0eSBhbmQgYWdlLCB3aXRoIHRoZSB1bml0IG9mIGFuYWx5c2lzIGJlaW5nIEVuZ2xhbmQgYW5kIFdhbGVzLiAKCmBgYHtyfQojIExvYWQgaW4gZGF0YXNldCAKCmRmMiA8LSByZWFkX2NzdignLi4vRGF0YS9HSV9hZ2UuY3N2JykKYGBgCgoKYGBge3J9CiMgQnJpZWYgZ2xpbXBzZSBvZiBkYXRhIHN0cnVjdHVyZQpoZWFkKGRmMiwgMTApCmBgYAoKYGBge3J9CiMgTGV0J3MgY2hlY2sgb3V0IHRoZSBkaW1lbnNpb25zCgpkaW0oZGYyKQpgYGAKCiMjIERhdGEgQ2xlYW5pbmcKCiogQ2xlYW4gY29sdW1uIG5hbWVzCiogRmlsdGVyIG91dCB1bmVjZXNzYXJ5IGNhdGVnb3JpZXMKKiBDbGVhbiBnZW5kZXIgaWRlbnRpdHkgY2F0ZWdvcnkgdmFsdWVzIC0gdG9vIHdvcmR5CiogRW5zdXJlIGdlbmRlcl9pZGVudGl0eSBjb2x1bW4gaXMgYSBmYWN0b3Igd2l0aCBsZXZlbHMgaW4gZGVzaXJlZCBvcmRlcgoqIENsZWFuIGFnZSBjYXRlZ29yeSB2YWx1ZXMgLSB0b28gd29yZHkKCldlJ2xsIHdoaXogdGhyb3VnaCB0aGlzLCBiZWNhdXNlIGl0J3MgdGhlIHNhbWUgc3R1ZmYgd2UgZGlkIGZvciB0aGUgbGFzdCBkYXRhc2V0LiAKCmBgYHtyfQojIHN0cl9yZXBsYWNlX2FsbCgpIG1ldGhvZCBmaW5kcyBhbGwgc3Vic3RyaW5ncyB3aGljaCBtYXRjaCB0aGUgcmVnZXggYW5kIHJlcGxhY2VzIHRoZW0gd2l0aCBlbXB0eSBzdHJpbmcKIyBGaXJzdCwgbGV0J3MgcmVwbGFjZSBhbnkgYnJhY2tldHMgd2l0aCBlbXB0eSBzdHJpbmdzCmNvbG5hbWVzKGRmMikgPC0gc3RyX3JlcGxhY2VfYWxsKGNvbG5hbWVzKGRmMiksICJcXHMqXFwoW14pXSpcXCkiLCAiIikKCiMgTG93ZXJjYXNlIGNvbHVtbiB0ZXh0IGFuZCByZXBsYWNlIGVtcHR5IHNwYWNlcyB3aXRoICJfIgpjb2xuYW1lcyhkZjIpIDwtIHRvbG93ZXIoY29sbmFtZXMoZGYyKSkKY29sbmFtZXMoZGYyKSA8LSBzdHJfcmVwbGFjZV9hbGwoY29sbmFtZXMoZGYyKSwgIiAiLCAiXyIpCgojIExldCdzIHNlZSBpZiBpdCB3b3JrZWQuLgpjb2xuYW1lcyhkZjIpCgpgYGAKCmBgYHtyfQojIEdldCByaWQgb2YgY29sdW1ucyB0aGF0IGRvIG5vdCBhcHBseQpkZjIgPC0gZGYyICU+JSAKICBmaWx0ZXIoZ2VuZGVyX2lkZW50aXR5X2NvZGUgIT0gLTgpIAoKIyBVc2UgdW5pcXVlIGFuZCBhY2Nlc3MgY29sdW1uIHRvIG91dHB1dCBpdHMgdW5pcXVlIHZhbHVlcwoKdW5pcXVlKGRmMiRnZW5kZXJfaWRlbnRpdHlfY29kZSkKYGBgCgpgYGB7cn0KIyBHZXQgcmlkIG9mIHJlZHVuZGFudCBhZ2UgY2F0ZWdvcnkKIyBGdXJ0aGVyIGZpbHRlciBkYXRhCmRmMiA8LSBkZjIgJT4lCiAgZmlsdGVyKGFnZV9jb2RlICE9IDEpCgpgYGAKCmBgYHtyfQojIENsZWFuIHVwIHRoZSB2YWx1ZXMgaW4gdGhlICdhZ2UnIGNvbHVtbi4gTGV0J3Mgc2hvcnRlbiB0aGVtLgoKIyBDaGFpbiBzdHJfcmVwbGFjZSgpIGNhbGxzIHRvZ2V0aGVyIHRvIGFwcGx5IG11bHRpcGxlIHN0cmluZyByZXBsYWNlbWVudHMgaW4gc3VjY2Vzc2lvbgojIEVhY2ggc3RyX3JlcGxhY2UoKSBjYWxsIGlzIGFwcGxpZWQgdG8gdGhlIHJlc3VsdCBvZiB0aGUgcHJldmlvdXMgb25lCmRmMiRhZ2UgPC0gZGYyJGFnZSAlPiUKICBzdHJfcmVwbGFjZSgnQWdlZCAnLCAnJykgJT4lCiAgc3RyX3JlcGxhY2UoJ3RvJywgJy0nKSAlPiUKICBzdHJfcmVwbGFjZSgneWVhcnMnLCAnJykgJT4lCiAgc3RyX3JlcGxhY2UoJ2FuZCBvdmVyJywgJysnKSAlPiUKICBzdHJfcmVwbGFjZSgnIC0gJywgJy0nKQoKIyBXZSBjYW4gcGFzcyBvdXIgZGYgdG8gdGhlIHNlbGVjdCBmdW5jdGlvbiwgd2hlcmUgd2Ugc3BlY2lmeSB0aGUgY29sdW1uIHdlJ3JlIGludGVyZXN0ZWQgaW4uCiMgVGhlbiwgd2UgcGlwZSB0aGUgb3V0cHV0IHRvIHRoZSBoZWFkIGZ1bmN0aW9uLgpkZjIgJT4lCiAgc2VsZWN0KGFnZSkgJT4lCiAgaGVhZCgpCmBgYAoKYGBge3J9CiMgVXNlIGNvbWJvIG9mIG11dGF0ZSBhbmQgcmVjb2RlIHRvIHJlcGxhY2UgbXVsdGlwbGUgdmFsdWVzIGluIGNvbHVtbgojIC5kZWZhdWx0IGVuc3VyZXMgdGhhdCBhbnkgdmFsdWUgbm90IG1hdGNoaW5nIHRob3NlIHNwZWNpZmllZCBhcmUgbGVmdCB1bmNoYW5nZWQKCmRmMiA8LSBkZjIgJT4lCiAgbXV0YXRlKGdlbmRlcl9pZGVudGl0eSA9IHJlY29kZShnZW5kZXJfaWRlbnRpdHksCiAgICAiR2VuZGVyIGlkZW50aXR5IHRoZSBzYW1lIGFzIHNleCByZWdpc3RlcmVkIGF0IGJpcnRoIiA9ICJDaXNnZW5kZXIiLAogICAgIkdlbmRlciBpZGVudGl0eSBkaWZmZXJlbnQgZnJvbSBzZXggcmVnaXN0ZXJlZCBhdCBiaXJ0aCBidXQgbm8gc3BlY2lmaWMgaWRlbnRpdHkgZ2l2ZW4iID0gIkdlbmRlciBpZGVudGl0eSBkaWZmZXJlbnQgZnJvbSBzZXgiLAogICAgLmRlZmF1bHQgPSBnZW5kZXJfaWRlbnRpdHkpKQoKCmBgYAoKCmBgYHtyfQoKdW5pcXVlKGRmMiRnZW5kZXJfaWRlbnRpdHkpCmBgYAoKYGBge3J9CiMgV2UgdXNlIGZhY3RvciB0byBjb252ZXJ0IGdlbmRlcl9pZGVudGl0eSBjb2x1bW4gdG8gYSBmYWN0b3Igd2l0aCBzcGVjaWZpZWQgbGV2ZWxzIAojIFRoaXMgdGVsbHMgUGxvdGx5IHRoZSBleGFjdCBvcmRlciBpbiB3aGljaCB0byBkaXNwbGF5IGNhdGVnb3JpZXMKCmRmMiRnZW5kZXJfaWRlbnRpdHkgPC0gZmFjdG9yKGRmMiRnZW5kZXJfaWRlbnRpdHksIGxldmVscyA9IGMoCiAgIkNpc2dlbmRlciIsCiAgIkdlbmRlciBpZGVudGl0eSBkaWZmZXJlbnQgZnJvbSBzZXgiLAogICJUcmFucyB3b21hbiIsCiAgIlRyYW5zIG1hbiIsCiAgIkFsbCBvdGhlciBnZW5kZXIgaWRlbnRpdGllcyIsCiAgIk5vdCBhbnN3ZXJlZCIKKSkKYGBgCgoKIyMgUXVlc3Rpb24KCkhvdyBpcyBnZW5kZXIgaWRlbnRpdHkgZGlzdHJpYnV0ZWQgYW1vbmcgZGlmZmVyZW50IGFnZSBncm91cHM/CgpTb21lIHN1YnF1ZXN0aW9ucyB0aGF0IHRoaXMgY2FuIGhlbHAgdXMgYW5zd2VyOgoKKiBXaGF0ICUgb2YgdHJhbnMgd29tZW4gYXJlIGFnZWQgMTYtMjQgeWVhcnM/CiogQXJlIG9sZGVyIGFnZSBncm91cHMgb3ZlciByZXByZXNlbnRlZCBpbiB0aGUgJ25vbi1yZXNwb25zZScgY2F0ZWdvcnk/CgojIyBEYXRhIHByZS1wcm9jZXNzaW5nCgojIyMgQ2FsY3VsYXRlIHBlcmNlbnRhZ2VzIAoKQmVsb3csIHdlIHVzZSB0aGUgZ3JvdXBfYnkgZnVuY3Rpb24gdG8gZ3JvdXAgdGhlIGRhdGEgYnkgJ2dlbmRlcl9pZGVudGl0eScgYW5kIGNhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSB3aXRoaW4gZWFjaCBncm91cC4gVGhlbiB0aGUgbXV0YXRlKCkgZnVuY3Rpb24gYWRkcyBhIG5ldyBjb2x1bW4gJ3BlcmNlbnRhZ2UnIHRvIGRmLCB3aGljaCAoZm9yIGVhY2ggZ3JvdXApIGRpdmlkZXMgdGhlIG9ic2VydmF0aW9uIGJ5IHRoZSBzdW0gb2Ygb2JzZXJ2YXRpb25zLCBtdWx0aXBsaWVzIGl0IGJ5IDEwMCwgYW5kIHJvdW5kcyBpdCB1cCB0byAyIGRlY2ltYWwgcG9pbnRzLiBXZSB0aGVuIHVzZSB0aGUgdW5ncm91cCBmdW5jdGlvbiB3aGVuIHdlJ3JlIGRvbmUgd2l0aCB0aGUgZ3JvdXBpbmcgb3BlcmF0aW9uLiAKCmBgYHtyfQpkZjIgPC0gZGYyICU+JQogIGdyb3VwX2J5KGdlbmRlcl9pZGVudGl0eSkgJT4lCiAgbXV0YXRlKHBlcmNlbnRhZ2UgPSByb3VuZCgob2JzZXJ2YXRpb24gLyBzdW0ob2JzZXJ2YXRpb24pICogMTAwKSwgMikpICU+JQogIHVuZ3JvdXAoKQoKaGVhZChkZjIpCmBgYAoKCiMjIEludGVyYWN0aXZlIGdyb3VwZWQgYmFyIGNoYXJ0CgpXaGVuIGNyZWF0aW5nIGdyb3VwZWQgYmFyIGNoYXJ0cywgdGhlcmUncyBhIGZldyBzdWJ0bGUgZGlmZmVyZW5jZXMgdGhhdCB5b3UnbGwgbmVlZCB0byBhY2NvdW50IGZvciBpbiB0aGUgY29kZS4KRmlyc3QsIHdlJ2xsIG5lZWQgdG8gbWFrZSBzdXJlIHRoaXMgaXMgYSBncm91cGVkIGJhciBjaGFydCwgd2hpY2ggd2UgY2FuIHNldCB3aXRoIHRoZSAnYmFybW9kZScgcGFyYW1ldGVyLgpTZWNvbmQsIHdlJ2xsIG5lZWQgYSB3YXkgdG8gY29sb3VyIGVhY2ggYmFyIGluIGVhY2ggZ3JvdXAsIGFjY29yZGluZyB0byBhZ2UgY2F0ZWdvcmllcywgd2hpY2ggd2UgY2FuIGRvIHdpdGggdGhlICdjb2xvcicgYW5kICdjb2xvcnMnIHBhcmFtZXRlcnMuCgpgYGB7cn0KIyBDcmVhdGUgYSBncm91cGVkIGJhciBjaGFydCB3aXRoIGhvdmVyIGluZm9ybWF0aW9uCmZpZzIgPC0gcGxvdF9seShkYXRhID0gZGYyLCB4ID0gfmdlbmRlcl9pZGVudGl0eSwgeSA9IH5wZXJjZW50YWdlLCB0eXBlID0gJ2JhcicsCiAgICAgICAgICAgICAgICMgY29sb3Igc3BlY2lmaWVzIHdoaWNoIHZhcmlhYmxlIHRvIGNvbG91ciBieQogICAgICAgICAgICAgICAjIGNvbG9ycyBzcGVjaWZpZXMgdGhlIGNvbG91ciBwYWxldHRlIHRvIHVzZSwgYW5kIGhvdyBtYW55IGNvbG91cnMgYXJlIHJlcXVpcmVkCiAgICAgICAgICAgICAgIGNvbG9yID0gfmFnZSwgY29sb3JzID0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKGxlbmd0aCh1bmlxdWUoZGYyJGFnZSkpLCAiU2V0MiIpLAogICAgICAgICAgICAgICBob3ZlcmluZm8gPSAndGV4dCcsCiAgICAgICAgICAgICAgIGhvdmVydGV4dCA9IH5wYXN0ZSgiT2JzZXJ2YXRpb246ICIsIG9ic2VydmF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxicj5QZXJjZW50YWdlOiAiLCBzcHJpbnRmKCIlLjJmJSUiLCBwZXJjZW50YWdlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8YnI+QWdlIGdyb3VwOiAiLCBhZ2UpLAogICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGxpbmUgPSBsaXN0KGNvbG9yID0gJ3JnYmEoMjU1LDI1NSwyNTUsIDAuNSknLCB3aWR0aCA9IDAuNSkpLAogICAgICAgICAgICAgICB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gNjAwKQogICAgICAgICAgICAgICAKYGBgCgpgYGB7cn0KZmlnMgpgYGAKCmBgYHtyfQpmaWcyIDwtIGxheW91dChmaWcyLHRpdGxlID0gJ0Rpc3RyaWJ1dGlvbiBvZiBHZW5kZXIgSWRlbnRpdHkgQ2F0ZWdvcmllcyBBbW9uZyBBZ2UgR3JvdXBzJywKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gJ0dlbmRlciBJZGVudGl0eScpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnUGVyY2VudGFnZScpLAogICAgICAgICBsZWdlbmQgPSBsaXN0KHRpdGxlID0gbGlzdCh0ZXh0ID0gJ0FnZSBHcm91cCcpKSkKCmBgYAoKYGBge3J9CmZpZzIKYGBgCgojIyBTdGFja2VkIGJhciBjaGFydAoKVGhlIG1ldGhvZCBJIHNob3cgYmVsb3cgc2ltcGx5IGNvbnZlcnRzIHRoZSBwcmV2aW91c2x5IG1hZGUgZ3JvdXBlZCBiYXIgY2hhcnQgJ2ZpZzInIHRvIGEgc3RhY2tlZCBiYXIgY2hhcnQuIFN0YWNrZWQgYmFyIGNoYXJ0cyBjYW4gb25seSBiZSBjcmVhdGVkIHVzaW5nIHRoZSBsYXlvdXQoKSBmdW5jdGlvbiB0byBjaGFuZ2UgdGhlIGJhcm1vZGUsIGFzIHRoZSBkZWZhdWx0IGlzIGEgZ3JvdXBlZCBiYXIgY2hhcnQuIAoKCgpgYGB7cn0KIyBDb252ZXJ0IHRvIHN0YWNrZWQgYmFyIGNoYXJ0CgpzdF9maWcgPC0gbGF5b3V0KGZpZzIsCiAgICAgICAgIGJhcm1vZGUgPSAnc3RhY2snKQoKc3RfZmlnCmBgYAoKCgojIyBEYXRhc2V0IDMKClRoaXMgZGF0YXNldCBjbGFzc2lmaWVzIHJlc2lkZW50cyBieSBnZW5kZXIgaWRlbnRpdHkgYW5kIGV0aG5pYyBncm91cCwgd2l0aCB0aGUgdW5pdCBvZiBhbmFseXNpcyBiZWluZyB0aGUgMzMxIGxvY2FsIGF1dGhvcml0aWVzIGFjcm9zcyBFbmdsYW5kIGFuZCBXYWxlcy4gCgpgYGB7cn0KIyBMb2FkIGluIGRhdGFzZXQKCmRmMyA8LSByZWFkX2NzdignLi4vRGF0YS9HSV9ldGhuaWMuY3N2JykKYGBgCgoKYGBge3J9CiMgQnJpZWYgZ2xpbXBzZSBhdCB1bmRlcmx5aW5nIGRhdGEgc3RydWN0dXJlCmhlYWQoZGYzLCAxMCkKYGBgCgojIyBEYXRhIENsZWFuaW5nCgoqIENsZWFuIGNvbHVtbiBuYW1lcwoqIEZpbHRlciBvdXQgdW5uZWNlc3NhcnkgY2F0ZWdvcmllcwoKQmVsb3csIEkgcHJvdmlkZSBhbm90aGVyIG1ldGhvZCAnZ3N1YigpJyB3aGljaCBjYW4gYmUgdXNlZCBpbnN0ZWFkIG9mIHRoZSBzdHJfcmVwbGFjZV9hbGwoKSBtZXRob2Qgd2hpY2ggaGFzIGJlZW4gZGVtb25zdHJhdGVkIGluIHRoZSBwcmV2aW91cyBjbGVhbmluZyBzZWN0aW9ucy4gQmFzaWNhbGx5LCBsb29rcyBmb3IgYSBwYXR0ZXJuIGFuZCBhcHBsaWVzIHRoZSByZXBsYWNlbWVudCB0byBhbnkgY29sdW1uIG5hbWVzIHdoaWNoIG1hdGNoIHRoZSBwYXR0ZXJuLiAKCmBgYHtyfQojIFJlbW92ZSBhbGwgdGV4dCB3aXRoaW4gcGFyZW50aGVzZXMgZnJvbSBjb2x1bW4gbmFtZXMgYW5kIHJlcGxhY2UgaXQgd2l0aCBhbiBlbXB0eSBzdHJpbmcKCiMgdGlsZGUgb3BlcmF0b3IgKH4pIHVzZWQgdG8gYXBwbHkgZnVuY3Rpb24gJ2dzdWInIHRvIGVhY2ggY29sbmFtZQojIC54IHJlcHJlc2VudHMgZWFjaCBjb2xuYW1lIHRoYXQgZ3N1YiB3aWxsIGJlIGFwcGxpZWQgdG8KZGYzIDwtIGRmMyAlPiUgCiAgcmVuYW1lX3dpdGgofiBnc3ViKCJcXHMqXFwoW14pXSpcXCkiLCAiIiwgLngpKQpgYGAKCmBgYHtyfQojIExvd2VyY2FzZSBhbGwgdGV4dCBpbiBjb2x1bW4gbmFtZXMgYW5kIHJlcGxhY2Ugc3BhY2VzIHdpdGggdW5kZXJzY29yZXMKZGYzIDwtIGRmMyAlPiUgCiAgcmVuYW1lX3dpdGgofiB0b2xvd2VyKGdzdWIoIiAiLCAiXyIsIC54KSkpCmBgYAoKYGBge3J9CiMgU2hvcnRlbiB0aGUgbG9jYWwgYXV0aG9yaXR5IGNvbHVtbiBuYW1lcyBhcyB0aGV5IGFyZSB3YXkgdG9vIGxvbmcKZGYzIDwtIGRmMyAlPiUgCiAgcmVuYW1lKExBX2NvZGUgPSBsb3dlcl90aWVyX2xvY2FsX2F1dGhvcml0aWVzX2NvZGUsCiAgICAgICAgIExBX25hbWUgPSBsb3dlcl90aWVyX2xvY2FsX2F1dGhvcml0aWVzKQoKYGBgCgpgYGB7cn0KIyBMZXQncyBzZWUgaWYgaXQgd29ya2VkCmNvbG5hbWVzKGRmMykKYGBgCgoKYGBge3J9CiMgUmVtb3ZlICdEb2VzIG5vdCBhcHBseScgY2F0ZWdvcmllcyBmb3IgdGhlIGdlbmRlciBpZGVudGl0eSBhbmQgZXRobmljIGdyb3VwIGNvbHVtbnMKZGYzIDwtIGRmMyAlPiUgCiAgZmlsdGVyKGdlbmRlcl9pZGVudGl0eV9jb2RlICE9IC04LCBldGhuaWNfZ3JvdXBfY29kZSAhPSAtOCkKYGBgCgpgYGB7cn0KIyBMZXQncyBzZWUgaWYgaXQgd29ya2VkLi4gCnVuaXF1ZShkZjMkZ2VuZGVyX2lkZW50aXR5X2NvZGUpCmBgYAoKYGBge3J9CiMgTGV0J3Mgc2VlIGlmIGl0IHdvcmtlZC4uCnVuaXF1ZShkZjMkZXRobmljX2dyb3VwX2NvZGUpCmBgYAoKIyMgUXVlc3Rpb24KCkhvdyBkb2VzIHRoZSByYXRlIG9mICdub24tcmVzcG9uc2UnIG9uIGdlbmRlciBpZGVudGl0eSB2YXJ5IGFtb25nIGRpZmZlcmVudCBldGhuaWMgZ3JvdXBzIGFjcm9zcyBsb2NhbCBhdXRob3JpdGllcyBpbiBFbmdsYW5kIGFuZCBXYWxlcz8KCkEgc3VicXVlc3Rpb24gdGhpcyBjb3VsZCBoZWxwIHVzIGFuc3dlcjoKCkRvZXMgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG5vbi1yZXNwb25zZSBhbmQgZXRobmljIGdyb3VwICUgZm9yIGxvY2FsIGF1dGhvcml0aWVzIGRpZmZlciBiZXR3ZWVuIHRoZSAnV2hpdGUnIGNhdGVnb3JpZXMgYW5kIG90aGVyIGV0aG5pYyBncm91cHM/CgojIyBEYXRhIHByZS1wcm9jZXNzaW5nCgpHaXZlbiB0aGF0IEkgd2FudCB0byBleHBsb3JlIHRoZSBxdWVzdGlvbiBhYm92ZSwgSSdkIGxpa2UgdG8gY3JlYXRlIGEgc2NhdHRlcnBsb3Qgd2hpY2ggZXhwbG9yZXMgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSAlIG9mIGNlcnRhaW4gZXRobmljIGdyb3VwcyB3aXRoaW4gbG9jYWwgYXV0aG9yaXRpZXMgYW5kIHRoZWlyIG5vbi1yZXNwb25zZSByYXRlcy4gVGhlcmVmb3JlLCBJJ2xsIG5lZWQgdG8gcHJlcCBteSB4IGFuZCB5IHZhcmlhYmxlcywgc28gSSdsbCBuZWVkIHRvIGNhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiBlYWNoIGV0aG5pYyBncm91cCBpbiBlYWNoIExBLCBhbmQgdGhhdCBldGhuaWMgZ3JvdXBzIG5vbi1yZXNwb25zZSByYXRlIHdpdGhpbiBlYWNoIExBLiAgCgojIyMgQ2FsY3VsYXRlICUgb2YgZWFjaCBldGhuaWMgZ3JvdXAgaW4gZWFjaCBMQQoKYGBge3J9CiMgRmlyc3QsIHdlJ3JlIGdvaW5nIHRvIGdyb3VwIG91ciBkYXRhIGJ5IExBX25hbWUsIGV0aG5pYyBncm91cCwgYW5kIHN1bSBvdXIgb2JzZXJ2YXRpb25zCiMgVGhpcyBsZWF2ZXMgdXMgd2l0aCB0aGUgdG90YWwgb2YgZWFjaCBldGhuaWMgZ3JvdXAgaW4gZWFjaCBsb2NhbCBhdXRob3JpdHkKZXRobmljX3RvdGFscyA8LSBkZjMgJT4lCiAgZ3JvdXBfYnkoTEFfbmFtZSwgZXRobmljX2dyb3VwKSAlPiUKICBzdW1tYXJpc2UoRXRobmljX3N1bSA9IHN1bShvYnNlcnZhdGlvbiwgbmEucm0gPSBUUlVFKSkgJT4lCiAgdW5ncm91cCgpCgojIFByaW50IHRoZSBmaXJzdCBmZXcgcm93cyB0byBjaGVjawpoZWFkKGV0aG5pY190b3RhbHMpCmBgYAoKCmBgYHtyfQojIENhbGN1bGF0ZSB0b3RhbCBvYnNlcnZhdGlvbnMgZm9yIGVhY2ggbG9jYWwgYXV0aG9yaXR5IGJ5IGdyb3VwaW5nIGRmMyBieSBsb2NhbCBhdXRob3JpdHkgYW5kIHN1bW1pbmcgdXAgb2JzCmxhX3RvdGFscyA8LSBkZjMgJT4lCiAgZ3JvdXBfYnkoTEFfbmFtZSkgJT4lCiAgc3VtbWFyaXNlKExBX3N1bSA9IHN1bShvYnNlcnZhdGlvbiwgbmEucm0gPSBUUlVFKSkgJT4lCiAgdW5ncm91cCgpCgojIFByaW50IHRoZSBmaXJzdCBmZXcgcm93cyB0byBjaGVjawpoZWFkKGxhX3RvdGFscykKYGBgCgpgYGB7cn0KIyBNZXJnZSB0aGUgZXRobmljX3RvdGFscyBhbmQgbGFfdG90YWxzIGRhdGFmcmFtZXMgdG9nZXRoZXIKIyBieSBwYXJhbWV0ZXIgc3BlY2lmaWVzIHdoaWNoIGNvbHVtbiB0byBwZXJmb3JtIG1lcmdlIG9uCgpncnBfcGN0IDwtIG1lcmdlKGV0aG5pY190b3RhbHMsIGxhX3RvdGFscywgYnkgPSAiTEFfbmFtZSIpCmBgYAoKYGBge3J9CiMgQ2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIG9mIGVhY2ggZXRobmljIGdyb3VwIHdpdGhpbiBlYWNoIGxvY2FsIGF1dGhvcml0eQojIFN0b3JlIHJlc3VsdHMgaW4gbmV3IGNvbHVtbiAKCmdycF9wY3QgPC0gZ3JwX3BjdCAlPiUKICBtdXRhdGUoUGVyY2VudGFnZSA9IHJvdW5kKChFdGhuaWNfc3VtIC8gTEFfc3VtICogMTAwKSwgMikpCmBgYAoKCmBgYHtyfQojIFByaW50IHRoZSBmaXJzdCBmZXcgcm93cyB0byBjaGVjawpoZWFkKGdycF9wY3QsIDEwKQpgYGAKCiMjIyBDYWxjdWxhdGUgRXRobmljIEdyb3VwIE5vbi1SZXNwb25zZSBSYXRlcyAoJSdzKSBXaXRoaW4gTEFzCgpgYGB7cn0KIyBXZSBhbHJlYWR5IGhhdmUgb3VyIGV0aG5pYyBncm91cCB0b3RhbHMgd2hpY2ggd2UgY2FuIHJlLXVzZS4uLgoKZXRobmljX3RvdGFscwpgYGAKCmBgYHtyfQojIENhbGN1bGF0ZSBzdW0gb2Ygbm9uLXJlc3BvbnNlcyBmb3IgZWFjaCBldGhuaWMgZ3JvdXAgd2l0aGluIGVhY2ggTEEKIyBGaWx0ZXIgZGYzIHNvIHRoYXQgd2Ugb25seSBoYXZlIG5vbi1yZXNwb25zZSByb3dzCiMgR3JvdXAgYnkgTEEgYW5kIGV0aG5pYyBncm91cCB0aGVuIHN1bSBub24tcmVzcG9uc2Ugb2JzIGFuZCBzdG9yZSB0aGUgcmVzdWx0cyBpbiBuZXcgY29sdW1uCgpub25fcmVzcG9uc2VfdG90YWxzIDwtIGRmMyAlPiUKICBmaWx0ZXIoZ2VuZGVyX2lkZW50aXR5ID09ICdOb3QgYW5zd2VyZWQnKSAlPiUKICBncm91cF9ieShMQV9uYW1lLCBldGhuaWNfZ3JvdXApICU+JQogIHN1bW1hcmlzZShOUl90b3RhbCA9IHN1bShvYnNlcnZhdGlvbiwgbmEucm0gPSBUUlVFKSkgJT4lCiAgdW5ncm91cCgpCmBgYAoKYGBge3J9CgojIExldCdzIGNoZWNrIGl0IG91dC4uIApoZWFkKG5vbl9yZXNwb25zZV90b3RhbHMpCmBgYAoKCmBgYHtyfQojIE1lcmdlIHRoZSB0ZXRobmljIGdyb3VwIHRvdGFscyB3aXRoIHRoZSBldGhuaWMgZ3JvdXAgbm9uLXJlc3BvbnNlIHRvdGFscwojIGMgLSB1c2VkIHdoZW4gd2UncmUgcmVmZXJlbmNpbmcgbW9yZSB0aGFuIG9uZSBjb2x1bW4KIyBhbGwueCAtIHBlcmZvcm1zIGEgbGVmdCBqb2luCmdycF9uciA8LSBtZXJnZShldGhuaWNfdG90YWxzLCBub25fcmVzcG9uc2VfdG90YWxzLCBieSA9IGMoIkxBX25hbWUiLCAiZXRobmljX2dyb3VwIiksIGFsbC54ID0gVFJVRSkKCmBgYAoKYGBge3J9CiMgTGV0J3MgY2hlY2sgaXQgb3V0Li4gCgpoZWFkKGdycF9ucikKYGBgCgoKYGBge3J9CiMgQ2FsY3VsYXRlIHRoZSBub24tcmVzcG9uc2UgcGVyY2VudGFnZSBmb3IgZWFjaCBldGhuaWMgZ3JvdXAgd2l0aGluIGVhY2ggTEEKIyBTdG9yZSByZXN1bHRzIGluIG5ldyBjb2x1bW4KCmdycF9uciA8LSBncnBfbnIgJT4lCiAgbXV0YXRlKEV0aF9OUl9QZXJjID0gcm91bmQoKE5SX3RvdGFsIC8gRXRobmljX3N1bSAqIDEwMCksIDIpKQpgYGAKCgpgYGB7cn0KIyBRdWljayBnbGFuY2UuLiAKaGVhZChncnBfbnIpCmBgYAojIyMgTWVyZ2UgYm90aCBkYXRhc2V0cwoKTm93IHRoYXQgd2UndmUgY29tcGxldGVkIHRoZSBuZWNlc3NhcnkgY2FsY3VsYXRpb25zLCB3ZSBhcmUgbGVmdCB3aXRoIHR3byBkYXRhc2V0czoKCiogZ3JwX3BjdCAtIGRldGFpbHMgdGhlICUgb2YgZWFjaCBldGhuaWNfZ3JvdXAgaW4gZWFjaCBMQQoqIGdycF9uciAtIGRldGFpbHMgdGhlIGV0aG5pYyBncm91cCBub24tcmVzcG9uc2UgJSBpbiBlYWNoIExBCgpBbGwgd2UgbmVlZCB0byBkbyBub3cgdGhlbiwgaXMgbWVyZ2UgdGhlc2UgZGF0YXNldHMgdG9nZXRoZXIgc28gdGhhdCB3ZSBjYW4gYWNjZXNzIHRoZSBuZXcgY29sdW1ucyBhbmQgcGxvdCB0aGVtOgoKKiBQZXJjZW50YWdlCiogRXRoX05SX1BlcmMKCmBgYHtyfQojIE1lcmdlIHRoZSBub24tcmVzcG9uc2UgZGF0YSB3aXRoIHRoZSBwZXJjZW50YWdlIG9mIGVhY2ggZXRobmljIGdyb3VwIHdpdGhpbiBlYWNoIExBCiMgVXNlIHNlbGVjdCB0byBpc29sYXRlIGNvbHVtbnMgSSB3YW50IHRvIHByZXNlcnZlIGluIHRoZSBtZXJnZSwgTEFfc3VtIGlzIHJlZHVuZGFudC4uLgoKbnIgPC0gbWVyZ2UoZ3JwX25yLCBzZWxlY3QoZ3JwX3BjdCwgTEFfbmFtZSwgZXRobmljX2dyb3VwLCBQZXJjZW50YWdlKSwgYnkgPSBjKCJMQV9uYW1lIiwgImV0aG5pY19ncm91cCIpKQpgYGAKCmBgYHtyfQojIFF1aWNrIGdsYW5jZQoKaGVhZChucikKYGBgCgoKIyMgSW50ZXJhY3RpdmUgc2NhdHRlcnBsb3QKCkluIHRoaXMgc2VjdGlvbiB3ZSdyZSBnb2luZyB0bzoKCjEuIENyZWF0ZSBhIHNpbXBsZSBzY2F0dGVycGxvdCBleHBsb3JpbmcgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBwZXJjZW50YWdlIG9mIGFzaWFuIGNpdGl6ZW5zIHdpdGhpbiBsb2NhbCBhdXRob3JpdGllcyBhbmQgdGhlaXIgbm9uLXJlc3BvbnNlIHJhdGVzCgoyLiBJbXBsZW1lbnQgYSBkcm9wZG93biB3aWRnZXQgdG8gdXBkYXRlIG91ciBzY2F0dGVycGxvdCAKCmBgYHtyfQojIFN1YnNldCBkYXRhZnJhbWUgc28gd2Ugb25seSBoYXZlIHJlc3BvbnNlcyBmcm9tIHRoZSBhc2lhbiBldGhuaWMgZ3JvdXAKCmFzaWFuIDwtIG5yICU+JQogIGZpbHRlcihldGhuaWNfZ3JvdXAgPT0gJ0FzaWFuLCBBc2lhbiBCcml0aXNoIG9yIEFzaWFuIFdlbHNoJykKYGBgCgoKYGBge3J9CiMgQ2hlY2sgaXQgb3V0Li4gCgpoZWFkKGFzaWFuKQpgYGAKCmBgYHtyfQojIEluaXRpYWxpemUgZmlndXJlCmZpZzMgPC0gcGxvdF9seShkYXRhID0gYXNpYW4sCiAgICAgICAgICAgICAgIHggPSB+UGVyY2VudGFnZSwKICAgICAgICAgICAgICAgeSA9IH5FdGhfTlJfUGVyYywKICAgICAgICAgICAgICAgdGV4dCA9IH5wYXN0ZSgnTEEgTmFtZTonLCBMQV9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+Tm9uLXJlc3BvbnNlIFRvdGFsOicsIE5SX3RvdGFsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+RXRobmljIEdyb3VwIFRvdGFsOicsIEV0aG5pY19zdW0pLAogICAgICAgICAgICAgICBob3ZlcmluZm8gPSAidGV4dCIsCiAgICAgICAgICAgICAgIG1vZGUgPSAnbWFya2VycycsICAjIFNwZWNpZnkgbWFya2VyIHBvaW50cwogICAgICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCAgIyBHcmFwaCB0eXBlIC0gc2NhdHRlcnBsb3QKICAgICAgICAgICAgICAgbmFtZSA9ICdBc2lhbicpICAjIERlZmF1bHQgdmlzaWJsZSBncmFwaAoKCiMgQ3VzdG9taXplIGxheW91dCAKZmlnMyA8LSBmaWczICU+JQogIGxheW91dCh0aXRsZSA9ICdOb24tUmVzcG9uc2UgUmF0ZXMgb2YgdGhlIEFzaWFuIEV0aG5pYyBHcm91cCBBY3Jvc3MgTG9jYWwgQXV0aG9yaXRpZXMnLAogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAnUGVyY2VudGFnZSBvZiBFdGhuaWMgR3JvdXAnKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ05vbi1yZXNwb25zZSBSYXRlJyksCiAgICAgICAgIHdpZHRoID0gNzAwLAogICAgICAgICBoZWlnaHQgPSA3MDApCgojIFNob3cgdGhlIHBsb3QKZmlnMwpgYGAKCiMjIERyb3Bkb3duIHNlbGVjdGlvbgoKV2hhdCB3ZSdyZSBnb2luZyB0byBkbyBub3csIGlzIHVzZSBQbG90bHkncyAndXBkYXRlbWVudXMnIGluIGNvbmp1bmN0aW9uIHdpdGggdGhlICd1cGRhdGUnIG1ldGhvZCB0byBjcmVhdGUgYSBkcm9wZG93biB3aGVyZSB3ZSBjYW4gc3dpdGNoIGJldHdlZW4gdGhlIEFzaWFuIGV0aG5pYyBncm91cCwgYW5kIHRoZSBXaGl0ZSBldGhuaWMgZ3JvdXAgdG8gbWFrZSBzb21lIGNvbXBhcmlzb25zLiAKCiMjIyBTdGVwIDE6IEluaXRpYWxpc2UgZmlndXJlIGFuZCBhZGQgdHJhY2VzCgpXZSdsbCBzdGFydCBieSBjcmVhdGluZyBhIHBsb3RfbHkgZmlndXJlIHdpdGggbm8gZGF0YSBvciB2YXJpYWJsZXMgc3BlY2lmaWVkLiBUaGlzIGlzIGJlY2F1c2Ugd2UncmUgZ29pbmcgdG8gdXNlIGFkZF90cmFjZSB0byBhZGQgb3VyIHR3byBzZXRzIG9mIGRhdGFwb2ludHMgdG8gdGhlIHBsb3QuICdUcmFjZXMnIHJlZmVyIHRvIGEgc2V0IG9mIGRhdGEsIHNvIGluIG91ciBleGFtcGxlIHdlIHdhbnQgdG8gYWRkIGEgdHJhY2Ugd2l0aCB0aGUgZGF0YSBwb2ludHMgcmVsYXRpbmcgdG8gb3VyIGFzaWFuIGV0aG5pYyBncm91cCwgYW5kIGFub3RoZXIgb25lIGZvciBvdXIgd2hpdGUgZXRobmljIGdyb3VwLiBUaGlzIHdpbGwgc3RhcnQgdG8gbWFrZSBzZW5zZSB3aGVuIHdlIGxvb2sgYXQgdGhlIGNvZGUgYmVsb3cuIAoKYGBge3J9CiMgSW5pdGlhbGl6ZSBhIFBsb3RseSBmaWd1cmUKZmlnNCA8LSBwbG90X2x5KCkKCiMgTGV0J3MgdGFrZSBhIGxvb2suLgojIFRoaXMgaXMgb3VyIGJ1aWxkaW5nIGJsb2NrCmZpZzQKCmBgYAoKCgpgYGB7cn0KIyBTdWJzZXQgZGF0YWZyYW1lIHNvIHdlIG9ubHkgaGF2ZSByZXNwb25zZXMgZnJvbSB0aGUgd2hpdGUgZXRobmljIGdyb3VwCndoaXRlIDwtIG5yICU+JQogIGZpbHRlcihldGhuaWNfZ3JvdXAgPT0gJ1doaXRlOiBFbmdsaXNoLCBXZWxzaCwgU2NvdHRpc2gsIE5vcnRoZXJuIElyaXNoIG9yIEJyaXRpc2gnKQoKYGBgCgpgYGB7cn0KIyBRdWljayBjaGVjay4uLgpoZWFkKHdoaXRlKQpgYGAKCgpgYGB7cn0KIyBBZGQgdHJhY2UgZm9yIHRoZSBBc2lhbiBldGhuaWMgZ3JvdXAKCmZpZzQgPC0gZmlnNCAlPiUgYWRkX3RyYWNlKAogIGRhdGEgPSBhc2lhbiwKICB4ID0gflBlcmNlbnRhZ2UsCiAgeSA9IH5FdGhfTlJfUGVyYywKICB0ZXh0ID0gfnBhc3RlKCdMQSBOYW1lOicsIExBX25hbWUsCiAgICAgICAgICAgICAgICAnPGJyPk5vbi1yZXNwb25zZSBUb3RhbDonLCBOUl90b3RhbCwKICAgICAgICAgICAgICAgICc8YnI+RXRobmljIEdyb3VwIFRvdGFsOicsIEV0aG5pY19zdW0pLAogIHR5cGUgPSAnc2NhdHRlcicsCiAgbW9kZSA9ICdtYXJrZXJzJywKICBuYW1lID0gJ0FzaWFuJywKICBob3ZlcmluZm8gPSAndGV4dCcsCiAgdmlzaWJsZSA9IFQKKQoKIyBBZGQgdHJhY2UgZm9yIHRoZSBXaGl0ZSBldGhuaWMgZ3JvdXAKZmlnNCA8LSBmaWc0ICU+JSBhZGRfdHJhY2UoCiAgZGF0YSA9IHdoaXRlLAogIHggPSB+UGVyY2VudGFnZSwKICB5ID0gfkV0aF9OUl9QZXJjLAogIHRleHQgPSB+cGFzdGUoJ0xBIE5hbWU6JywgTEFfbmFtZSwKICAgICAgICAgICAgICAgICc8YnI+Tm9uLXJlc3BvbnNlIFRvdGFsOicsIE5SX3RvdGFsLAogICAgICAgICAgICAgICAgJzxicj5FdGhuaWMgR3JvdXAgVG90YWw6JywgRXRobmljX3N1bSksCiAgdHlwZSA9ICdzY2F0dGVyJywKICBtb2RlID0gJ21hcmtlcnMnLAogIG5hbWUgPSAnV2hpdGUnLAogIGhvdmVyaW5mbyA9ICd0ZXh0JywKICB2aXNpYmxlID0gRgopCgpmaWc0CmBgYAoKIyMjIFN0ZXAgMjogQ29uZmlndXJlIGRyb3Bkb3duIGJ1dHRvbnMgYW5kIHNldCBpbml0aWFsIHZpc2liaWxpdHkgb2YgZGF0YXNldHMKCmBgYHtyfQoKIyBEZWZpbmUgZHJvcGRvd24gYnV0dG9ucyBmb3IgaW50ZXJhY3Rpdml0eQpmaWc0IDwtIGZpZzQgJT4lIGxheW91dCgKICB0aXRsZSA9ICJOb24tUmVzcG9uc2UgUmF0ZXMgQWNyb3NzIExvY2FsIEF1dGhvcml0aWVzIiwKICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiUGVyY2VudGFnZSBvZiBFdGhuaWMgR3JvdXAiKSwKICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTm9uLXJlc3BvbnNlIFJhdGUiKSwKICAjIEhpZGUgdGhlIGxlZ2VuZCwgYXMgaW50ZXJhY3RpdmUgZHJvcGRvd24gd2lsbCBoYW5kbGUgdHJhY2UgdmlzaWJpbGl0eQogIHNob3dsZWdlbmQgPSBGQUxTRSwKICAjIEFkZCBkcm9wZG93biBtZW51IGZvciBpbnRlcmFjdGl2ZSBwbG90IHVwZGF0ZXMKICB1cGRhdGVtZW51cyA9IGxpc3QoCiAgICBsaXN0KAogICAgICB0eXBlID0gImRyb3Bkb3duIiwKICAgICAgYnV0dG9ucyA9IGxpc3QoCiAgICAgICAgbGlzdCgKICAgICAgICAgICMgdGhlIHVwZGF0ZSBtZXRob2QgY2hhbmdlcyBwbG90IGF0dHJpYnV0ZXMgd2hlbiBhIGJ1dHRvbiBpcyBjbGlja2VkCiAgICAgICAgICBtZXRob2QgPSAidXBkYXRlIiwKICAgICAgICAgICMgRmlyc3QgYnV0dG9uIG1ha2VzIEFzaWFuIGRhdGEgdmlzaWJsZSBhbmQgaGlkZXMgdGhlIFdoaXRlIGRhdGEKICAgICAgICAgIGFyZ3MgPSBsaXN0KGxpc3QoInZpc2libGUiID0gbGlzdChUUlVFLCBGQUxTRSkpLAogICAgICAgICAgICAgICAgICAgICAgIyBVcGRhdGUgdGhlIHRpdGxlIHNwZWNpZmljIHRvIHRoZSBBc2lhbiBkYXRhCiAgICAgICAgICAgICAgICAgICAgICBsaXN0KCJ0aXRsZSIgPSAiTm9uLVJlc3BvbnNlIFJhdGVzIG9mIHRoZSBBc2lhbiBFdGhuaWMgR3JvdXAgQWNyb3NzIExvY2FsIEF1dGhvcml0aWVzIikpLAogICAgICAgICAgIyBTcGVjaWZ5IGJ1dHRvbiBsYWJlbAogICAgICAgICAgbGFiZWwgPSAiQXNpYW4iCiAgICAgICAgKSwKICAgICAgICBsaXN0KAogICAgICAgICAgbWV0aG9kID0gInVwZGF0ZSIsCiAgICAgICAgICBhcmdzID0gbGlzdChsaXN0KCJ2aXNpYmxlIiA9IGxpc3QoRkFMU0UsIFRSVUUpKSwKICAgICAgICAgICAgICAgICAgICAgIGxpc3QoInRpdGxlIiA9ICJOb24tUmVzcG9uc2UgUmF0ZXMgb2YgdGhlIFdoaXRlIEV0aG5pYyBHcm91cCBBY3Jvc3MgTG9jYWwgQXV0aG9yaXRpZXMiKSksCiAgICAgICAgICBsYWJlbCA9ICJXaGl0ZSIKICAgICAgICApCiAgICAgICkKICAgICkKICApCikKCiMgRGlzcGxheSB0aGUgZmlndXJlCmZpZzQKYGBgCgojIERhdGFzZXQgMwoKVGhpcyBkYXRhc2V0IGluY2x1ZGVzIHNleHVhbCBpZGVudGl0eSBlc3RpbWF0ZXMgYnkgZ2VuZGVyIGZyb20gMjAxMCB0byAyMDE0LiBUaGlzIGlzIHByZXNlbnRlZCBhdCBhIFVLIGxldmVsLCBhbmQgYnJva2VuIGRvd24gYnkgRW5nbGFuZCwgV2FsZXMsIFNjb3RsYW5kIGFuZCBOb3J0aGVybiBJcmVsYW5kLiBJIHdhbnRlZCB0aGlzIGd1aWRlIHRvIGluY2x1ZGUgYSBkZW1vIG9mIGhvdyB0byBtYWtlIGludGVyYWN0aXZlIGxpbmUgZ3JhcGhzIHdpdGggZ2VuZGVyIGlkZW50aXR5IGRhdGEsIGJ1dCB1bmZvcnR1bmF0ZWx5IGdpdmVuIHRoaXMgaXMgb25seSB0aGUgZmlyc3QgeWVhciB0aGF0IHRoZSBPTlMgaGFzIGNvbGxlY3RlZCB0aGlzIGRhdGEgdGhhdCB3YXMgbm90IHBvc3NpYmxlLiBTbyBJIGZvdW5kIGEgZGF0YXNldCBmcm9tIDIwMTUgd2hpY2ggaW52b2x2ZXMgZXhwZXJpbWVudGFsIHN0YXRpc3RpY3MgdGhhdCBoYXZlIGJlZW4gdXNlZCBpbiB0aGUgSW50ZWdyYXRlZCBIb3VzZWhvbGQgU3VydmV5LiBGb3IgbW9yZSBpbmZvLCB5b3UgY2FuIGNoZWNrIG91dCB0aGlzIFtPTlMgbGlua10oaHR0cHM6Ly93d3cub25zLmdvdi51ay9wZW9wbGVwb3B1bGF0aW9uYW5kY29tbXVuaXR5L2N1bHR1cmFsaWRlbnRpdHkvc2V4dWFsaXR5L2RhdGFzZXRzL3NleHVhbGlkZW50aXR5YnlhZ2Vncm91cGJ5Y291bnRyeSkuIAoKYGBge3J9CiMgTG9hZCBpbiBkYXRhc2V0CgpkZjMgPC0gcmVhZF9jc3YoJy4uL0RhdGEvY2xlYW5lZF9zZXh1YWxpdHlfZGYuY3N2JykKYGBgCgpgYGB7cn0KIyBCcmllZiBnbGltcHNlIGF0IHVuZGVybHlpbmcgZGF0YSBzdHJ1Y3R1cmUKCmhlYWQoZGYzLCAxMCkKYGBgCgojIyBEYXRhIGNsZWFuaW5nCgpXaGVuIEkgZmlyc3QgZm91bmQgdGhpcyBkYXRhc2V0IGl0IHdhcyB2ZXJ5IG1lc3N5IGFuZCBmb3JtYXR0ZWQgdGVycmlibHksIHNvIEkgcGVyZm9ybWVkIHNvbWUgY2xlYW5pbmcgb24gaXQgaW4gYSBzZXBhcmF0ZSBqdXB5dGVyIG5vdGVib29rLCB0byBzYXZlIGNsdXR0ZXJpbmcgdGhpcyBvbmUgYW5kIGRpc3RyYWN0aW5nIGZyb20gdGhlIG1haW4gdHV0b3JpYWwuIElmIHlvdSdkIGxpa2UgdG8gc2VlIGhvdyBJIGNsZWFuZWQgaXQgdXAsIHBsZWFzZSBzZWUgdGhlIFsnRGF0YV9jbGVhbmluZ19zZXh1YWxpdHkuaXB5bmInXShEYXRhX2NsZWFuaW5nX3NleHVhbGl0eS5pcHluYikgbm90ZWJvb2suIAoKIyMgRGF0YSBwcmUtcHJvY2Vzc2luZwoKVGhlIG9ubHkgcHJlLXByb2Nlc3Npbmcgd2UncmUgZ29pbmcgdG8gZG8gaXMgc3Vic2V0IG91ciBkYXRhIGJ5IGNvdW50cnksIGFuZCBhbHNvIGNyZWF0ZSAyIHNlcGFyYXRlIGRhdGFzZXRzIGZvciBHZW5kZXIgPSBNZW4gYW5kIEdlbmRlciA9IFdvbWVuLiBJJ2xsIGV4cGxhaW4gd2h5IHRoaXMgc3RlcCBpcyBuZWVkZWQgc29vbi4gCgpgYGB7cn0KIyBGaWx0ZXIgZGF0YXNldCB0byBmb2N1cyBvbiBFbmdsYW5kCmVuZ2xhbmRfZGYgPC0gZGYzICU+JQogIGZpbHRlcihDb3VudHJ5ID09ICdFbmdsYW5kJykKYGBgCgpgYGB7cn0KIyBMZXQncyBjaGVjayBpdCB3b3JrZWQuLiAKCnVuaXF1ZShlbmdsYW5kX2RmJENvdW50cnkpCmBgYAoKYGBge3J9CiMgRnVydGhlciBmaWx0ZXIgZGF0YSBmb3IgZWFjaCBnZW5kZXIKCm1lbiA8LSBlbmdsYW5kX2RmICU+JSBmaWx0ZXIoR2VuZGVyID09ICJNZW4iKQp3b21lbiA8LSBlbmdsYW5kX2RmICU+JSBmaWx0ZXIoR2VuZGVyID09ICJXb21lbiIpCgojIExldCdzIGNoZWNrIGl0IHdvcmtlZAoKdW5pcXVlKG1lbiRHZW5kZXIpCnVuaXF1ZSh3b21lbiRHZW5kZXIpCmBgYAoKCiMjIEludGVyYWN0aXZlIGxpbmVncmFwaAoKQ3JlYXRpbmcgYSBzaW1wbGUgbGluZSBncmFwaCBpbiBwbG90bHkgaXMgcHJldHR5IGVhc3ksIGJ1dCB3aGVyZSBwbG90bHkgc3RydWdnbGVzIChpbiBSKSBpcyBpbiBoYW5kbGluZyBmYWNldCBwbG90cy4gQSBmYWNldCBwbG90IGlzIGEgdHlwZSBvZiB2aXN1YWxpc2F0aW9uIHRoYXQgZGl2aWRlcyBkYXRhIGludG8gc3VicGxvdHMgYmFzZWQgb24gY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBXaGF0IEknZCBsaWtlIHRvIGRvIGlzIGNyZWF0ZSBhIGZhY2V0IHBsb3Qgb2Ygc2V4dWFsaXR5IHBlcmNlbnRhZ2VzIGluIEVuZ2xhbmQgKDIwMTAtMjAxNCkgd2l0aCBpbmRpdmlkdWFsIHN1YnBsb3RzIGZvciBvdXIgdHdvIGdlbmRlcnMuIFRoaXMgaXMgYWNoaWV2ZWQgZWFzaWx5IGluIFB5dGhvbiBkdWUgdG8gdGhlIHBsb3RseS5leHByZXNzIG1vZHVsZSwgd2hpY2ggcHJvdmlkZXMgYSBzaW1wbGUgd2F5IHRvIGNyZWF0ZSBmYWNldCBwbG90cy4gVW5mb3J0dW5hdGVseSwgd2UnbGwgaGF2ZSB0byBnbyB0aHJvdWdoIGEgYml0IG1vcmUgb2YgYSBsb25nd2luZGVkIHJvdXRlLCB3aGVyZSB3ZSdsbCBtYW51YWxseSBjcmVhdGUgb3VyIGluZGl2aWR1YWwgcGxvdHMgZm9yIGVhY2ggZ2VuZGVyLCB0aGVuIGNvbWJpbmUgdGhlbSB1c2luZyB0aGUgc3VicGxvdCBmdW5jdGlvbi4gQWxzbywgcGxvdGx5LmV4cHJlc3MgYXV0b21hdGljYWxseSBtYW5hZ2VzIGxlZ2VuZHMgdG8gZW5zdXJlIHRoZXkncmUgdW5pZmllZCBhY3Jvc3MgZmFjZXRzLCBidXQgUidzIHBsb3RseSByZXF1aXJlcyB0aGF0IHdlIG1hbnVhbGx5IHN5bmMgdXAgdGhlc2UgbGVnZW5kcy4gV29tcCB3b21wLiBMZXQncyBnZXQgdG8gaXQuIAoKCgpgYGB7cn0KIyBDcmVhdGUgaW5kaXZpZHVhbCBwbG90IGZvciBlYWNoIGdlbmRlcgoKIyBDcmVhdGUgcGxvdHMgZm9yIGVhY2ggZ2VuZGVyCm1lbl9wbG90IDwtIHBsb3RfbHkobWVuLCAKICAgICAgICAgICAgICAgICAgICB4ID0gflllYXIsIAogICAgICAgICAgICAgICAgICAgIHkgPSB+UGVyY2VudGFnZSwgCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSB+U2V4dWFsaXR5LCAKICAgICAgICAgICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCAKICAgICAgICAgICAgICAgICAgICAjIG1vZGUgdXNlZCB0byBtYWtlIHN1cmUgb3VyIGRhdGEgcG9pbnRzIGFyZSBjb25uZWN0ZWQgYnkgbGluZXMgYWNyb3NzIHRoZSB5ZWFycwogICAgICAgICAgICAgICAgICAgIG1vZGUgPSAnbGluZXMrbWFya2VycycsIAogICAgICAgICAgICAgICAgICAgIGhvdmVyaW5mbyA9ICd0ZXh0JywKICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCJZZWFyOiIsIFllYXIsICI8YnI+UGVyY2VudGFnZToiLCBQZXJjZW50YWdlLCAiPGJyPlNleHVhbGl0eToiLCBTZXh1YWxpdHkpLAogICAgICAgICAgICAgICAgICAgICMgbGVnZW5kZ3JvdXAgcGFyYW1ldGVyIGVuc3VyZXMgdGhhdCBkYXRhIHBvaW50cyByZWxhdGluZyB0byB0aGUgc2FtZSBjYXRlZ29yeSBhcmUgc3luY2VkIGFjcm9zcyBwbG90cwogICAgICAgICAgICAgICAgICAgIGxlZ2VuZGdyb3VwID0gflNleHVhbGl0eSwKICAgICAgICAgICAgICAgICAgICAjIHNob3dsZWdlbmQgcGFyYW1ldGVyIHNldCB0byBUUlVFIG9ubHkgZm9yIHRoaXMgcGxvdCB0byBhdm9pZCBkdXBsaWNhdGUgbGVnZW5kcwogICAgICAgICAgICAgICAgICAgIHNob3dsZWdlbmQgPSBUUlVFKSAlPiUKICBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gJ1llYXInLCB0aWNrdmFscyA9IDIwMTA6MjAxNCwgdGlja3RleHQgPSAyMDEwOjIwMTQpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnUGVyY2VudGFnZScpLAogICAgICAgICAjIEhlcmUgd2UgYWRkIGFuIGFubm90YXRpb24gdG8gdGhlIGdyYXBoIHRvIGxhYmVsIHRoZSBmaXJzdCBzdWJwbG90ICJNZW4iCiAgICAgICAgICMgU2V0dGluZyB4cmVmIGFuZCB5cmVmIHRvICdwYXBlcicgc2ltcGx5IG1lYW5zIHRoZSBhbm5vdGF0aW9uIHdvbid0IG1vdmUgaWYgd2Ugem9vbSBpbiBvciBvdXQKICAgICAgICAgYW5ub3RhdGlvbnMgPSBsaXN0KAogICAgICAgICAgIGxpc3QoeCA9IDAuNSwgeSA9IDEuMDUsIHRleHQgPSAiTWVuIiwgc2hvd2Fycm93ID0gRkFMU0UsIHhyZWY9J3BhcGVyJywgeXJlZj0ncGFwZXInKSkpCgoKd29tZW5fcGxvdCA8LSBwbG90X2x5KHdvbWVuLCAKICAgICAgICAgICAgICAgICAgICAgIHggPSB+WWVhciwgCiAgICAgICAgICAgICAgICAgICAgICB5ID0gflBlcmNlbnRhZ2UsIAogICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSB+U2V4dWFsaXR5LCAKICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIAogICAgICAgICAgICAgICAgICAgICAgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgCiAgICAgICAgICAgICAgICAgICAgICBob3ZlcmluZm8gPSAndGV4dCcsCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCJZZWFyOiIsIFllYXIsICI8YnI+UGVyY2VudGFnZToiLCBQZXJjZW50YWdlLCAiPGJyPlNleHVhbGl0eToiLCBTZXh1YWxpdHkpLAogICAgICAgICAgICAgICAgICAgICAgbGVnZW5kZ3JvdXAgPSB+U2V4dWFsaXR5LAogICAgICAgICAgICAgICAgICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gJ1llYXInLCB0aWNrdmFscyA9IDIwMTA6MjAxNCwgdGlja3RleHQgPSAyMDEwOjIwMTQpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnUGVyY2VudGFnZScpLAogICAgICAgICBhbm5vdGF0aW9ucyA9IGxpc3QoCiAgICAgICAgICAgbGlzdCh4ID0gMC41LCB5ID0gMS4wNSwgdGV4dCA9ICJXb21lbiIsIHNob3dhcnJvdyA9IEZBTFNFLCB4cmVmPSdwYXBlcicsIHlyZWY9J3BhcGVyJykpKQoKIyBMZXQncyB0YWtlIGEgbG9vayBhdCBvbmUgb2YgdGhlc2UgZ3JhcGhzCgp3b21lbl9wbG90CmBgYAoKYGBge3J9CiMgQ29tYmluZSBpbmRpdmlkdWFsIHBsb3RzIHVzaW5nIHN1YnBsb3QKIyBXaXRoaW4gc3VicGxvdCwgZGVmaW5lIG51bWJlciBvZiByb3dzLCBtYWtlIHN1cmUgc2hhcmUgc2FtZSB4IGF4ZXMgYW5kIGJvdGggYXhlcyB0aXRsZXMKZmlnNSA8LSBzdWJwbG90KG1lbl9wbG90LCB3b21lbl9wbG90LCBucm93cyA9IDIsIHNoYXJlWCA9IFRSVUUsIHRpdGxlWCA9IFRSVUUsIHRpdGxlWSA9IFRSVUUpICU+JQogIGxheW91dCgKICAgIHRpdGxlID0gbGlzdCgKICAgICAgdGV4dCA9ICdTZXh1YWxpdHkgUGVyY2VudGFnZXMgYnkgR2VuZGVyIGluIEVuZ2xhbmQgKDIwMTAtMjAxNCknLCAKICAgICAgeSA9IDAuOTgsICAjIE1vdmUgdGhlIHRpdGxlIGhpZ2hlciB1cAogICAgICB4ID0gMC41LCAgICMgQ2VudGVyIHRoZSB0aXRsZQogICAgICB4YW5jaG9yID0gImNlbnRlciIsCiAgICAgIHlhbmNob3IgPSAidG9wIgogICAgKSwKICAgIG1hcmdpbiA9IGxpc3QodCA9IDEwMCksICAjIEFkZCBzcGFjZSBhdCB0aGUgdG9wIGZvciB0aGUgdGl0bGUKICAgIGhlaWdodCA9IDgwMCwKICAgIHdpZHRoID0gMTAwMAogICkKCmZpZzUKYGBgCgojIFNoYXJpbmcgeW91ciBpbnRlcmFjdGl2ZSBncmFwaHMgb25saW5lIAoKSSdtIGdvaW5nIHRvIHByb3ZpZGUgeW91IGZpcnN0IHdpdGggYSByZWFsbHkgc2ltcGxlIHdheSB0byBob3N0IFBsb3RseSBncmFwaHMgc3BlY2lmaWNhbGx5LCB0aGVuIHdlJ2xsIGxvb2sgaW50byBvdGhlciBtb3JlIGNvbXBsZXggb3B0aW9ucyB0aGF0IHdvcmsgd2l0aCBtYW55IHZpc3VhbGlzYXRpb24gcGFja2FnZXMuCgoxLiBVc2UgUGxvdGx5J3MgWydDaGFydCBTdHVkaW8nXShodHRwczovL2NoYXJ0LXN0dWRpby5wbG90bHkuY29tLykuIFlvdSBjYW4gdXBsb2FkIHlvdXIgdmlzdWFsaXNhdGlvbnMgZGlyZWN0bHkgZnJvbSB5b3VyIGNvZGluZyBlbnZpcm9ubWVudCBhbmQgdGhlbiBnZXQgYSBsaW5rIHRvIHNoYXJlIHRoZW0gb25saW5lLiBZb3UnbGwgbmVlZCB0byBzaWduIHVwIGZvciBhbiBhY2NvdW50IGJ1dCBpdCdzIGZyZWUsIHVubGVzcyB5b3Ugd2FudCB0byBzaGFyZSB0aGUgbGluayBwcml2YXRlbHkgdGhlbiB5b3UnbGwgbmVlZCB0byB1cGdyYWRlIHlvdXIgYWNjb3VudC4gT3RoZXJ3aXNlLCBmb3IgZGF0YSB0aGF0J3MgZmluZSBiZWluZyBvdXQgaW4gdGhlIG9wZW4sIHRoaXMgaXMgYSBnb29kIG9wdGlvbi4KCjIuIEVtYmVkIHlvdXIgZ3JhcGhzIGluIEdpdEh1YiBwYWdlcy4KCgo=